|
|
import { Component, OnInit } from '@angular/core';
|
|
|
import { CommonModule, NgSwitch, NgSwitchCase, NgSwitchDefault } from '@angular/common';
|
|
|
import { FormsModule } from '@angular/forms';
|
|
|
import { MatchingService, ApiMatchResponse, ApiMatchItem } from '../matching.service';
|
|
|
import { ActivatedRoute, Router } from '@angular/router';
|
|
|
|
|
|
interface Profile {
|
|
|
user_id: number;
|
|
|
name: string;
|
|
|
age?: number;
|
|
|
city?: string;
|
|
|
job?: string;
|
|
|
photo_url?: string;
|
|
|
isOnline?: boolean;
|
|
|
match_score?: number;
|
|
|
score?: number;
|
|
|
gender?: string;
|
|
|
blue?: number;
|
|
|
green?: number;
|
|
|
yellow?: number;
|
|
|
red?: number;
|
|
|
explanations?: string[];
|
|
|
explanation_source?: string;
|
|
|
|
|
|
|
|
|
current_city?: string;
|
|
|
date_of_birth?: string;
|
|
|
job_role?: string;
|
|
|
employment_status?: string;
|
|
|
marital_status?: string;
|
|
|
education_level?: string;
|
|
|
expectation_score?: number;
|
|
|
character_score?: number;
|
|
|
|
|
|
|
|
|
full_name?: string;
|
|
|
height?: string;
|
|
|
skin_tone?: string;
|
|
|
languages_spoken?: string;
|
|
|
country?: string;
|
|
|
blood_group?: string;
|
|
|
religion?: string;
|
|
|
dual_citizenship?: string;
|
|
|
number_of_siblings?: string;
|
|
|
siblings_position?: string;
|
|
|
parents_living_status?: string;
|
|
|
live_with_parents?: string;
|
|
|
support_parents_financially?: string;
|
|
|
family_communication_frequency?: string;
|
|
|
food_preference?: string;
|
|
|
smoking_habit?: string;
|
|
|
alcohol_habit?: string;
|
|
|
daily_routine?: string;
|
|
|
fitness_level?: string;
|
|
|
own_pets?: string;
|
|
|
travel_preference?: string;
|
|
|
relaxation_mode?: string;
|
|
|
work_experience_years?: string;
|
|
|
career_aspirations?: string;
|
|
|
field_of_study?: string;
|
|
|
remark?: string;
|
|
|
hobbies_interests?: string;
|
|
|
conflict_approach?: string;
|
|
|
financial_style?: string;
|
|
|
income_range?: string;
|
|
|
relocation_willingness?: string;
|
|
|
family_type?: string;
|
|
|
children_timeline?: string;
|
|
|
open_to_adoption?: string;
|
|
|
deal_breakers?: string;
|
|
|
other_non_negotiables?: string;
|
|
|
health_constraints?: string;
|
|
|
live_with_inlaws?: string;
|
|
|
}
|
|
|
|
|
|
interface ChatMessage {
|
|
|
sender: 'me' | 'them';
|
|
|
text: string;
|
|
|
time: string;
|
|
|
}
|
|
|
|
|
|
interface CompatibilityAnalysis {
|
|
|
exactMatches: string[];
|
|
|
differences: FieldComparison[];
|
|
|
missingData: string[];
|
|
|
compatibilityPercent: number;
|
|
|
totalExactMatches: number;
|
|
|
totalDifferences: number;
|
|
|
totalMissingData: number;
|
|
|
}
|
|
|
|
|
|
interface FieldComparison {
|
|
|
fieldName: string;
|
|
|
yourExpectation: string;
|
|
|
theirProfile: string;
|
|
|
isCritical?: boolean;
|
|
|
}
|
|
|
|
|
|
@Component({
|
|
|
selector: 'app-matching-list',
|
|
|
standalone: true,
|
|
|
imports: [CommonModule, FormsModule, NgSwitch, NgSwitchCase, NgSwitchDefault],
|
|
|
templateUrl: './matching-list.component.html',
|
|
|
styleUrls: ['./matching-list.component.css']
|
|
|
})
|
|
|
export class MatchingListComponent implements OnInit {
|
|
|
activeTab: 'details' | 'compatibility' = 'details';
|
|
|
subTab: 'expectation' | 'character' = 'expectation';
|
|
|
showUserProfileModal: boolean = false;
|
|
|
private characterExplanationLoaded = false;
|
|
|
|
|
|
|
|
|
userId: number | null = null;
|
|
|
userName: string = 'User Name';
|
|
|
userCity: string = 'City';
|
|
|
userPhoto: string = 'https://cdn-icons-png.flaticon.com/512/3135/3135715.png';
|
|
|
userProfileCompletion: number = 75;
|
|
|
totalMatches: number = 0;
|
|
|
strengths: string[] = [];
|
|
|
risks: string[] = [];
|
|
|
sacrifices: string[] = [];
|
|
|
|
|
|
userProfileData: any = null;
|
|
|
userExpectationData: any = null;
|
|
|
showUserInfo: boolean = false;
|
|
|
|
|
|
|
|
|
loading = false;
|
|
|
error: string | null = null;
|
|
|
|
|
|
|
|
|
result: ApiMatchResponse | null = null;
|
|
|
groupedMatches: { range: string; items: ApiMatchItem[] }[] = [];
|
|
|
displayedMatches: Profile[] = [];
|
|
|
|
|
|
|
|
|
searchTerm: string = '';
|
|
|
|
|
|
|
|
|
availableLocations: string[] = [];
|
|
|
availableAgeRanges: string[] = ['18-25', '26-35', '36-45', '46+'];
|
|
|
availableProfessions: string[] = [];
|
|
|
availableEducationLevels: string[] = [];
|
|
|
availableReligions: string[] = [];
|
|
|
|
|
|
|
|
|
selectedLocation: string = '';
|
|
|
selectedProfession: string = '';
|
|
|
selectedEducation: string = '';
|
|
|
selectedReligion: string = '';
|
|
|
selectedAgeRange: string = '';
|
|
|
|
|
|
selectedRange: string = 'all';
|
|
|
availableRanges: string[] = ['all', '90-100', '80-89', '70-79', '60-69', 'below_60'];
|
|
|
|
|
|
mode: 'expectation-only' | 'expectation' | 'character' = 'character';
|
|
|
|
|
|
|
|
|
showProfileModal: boolean = false;
|
|
|
showEditModal: boolean = false;
|
|
|
showChatWindow: boolean = false;
|
|
|
showLikedDropdown: boolean = false;
|
|
|
|
|
|
|
|
|
selectedProfile: Profile | null = null;
|
|
|
likedProfiles: Profile[] = [];
|
|
|
|
|
|
|
|
|
currentChatUser: Profile | null = null;
|
|
|
chatMessages: ChatMessage[] = [];
|
|
|
newMessage: string = '';
|
|
|
showTyping: boolean = false;
|
|
|
|
|
|
|
|
|
editName: string = '';
|
|
|
editCity: string = '';
|
|
|
|
|
|
|
|
|
compatibilityAnalysis: CompatibilityAnalysis | null = null;
|
|
|
showExactMatches: boolean = false;
|
|
|
showDifferences: boolean = true;
|
|
|
|
|
|
|
|
|
setActiveTab(tab: 'details' | 'compatibility'): void {
|
|
|
this.activeTab = tab;
|
|
|
|
|
|
|
|
|
if (tab === 'compatibility' && this.selectedProfile) {
|
|
|
console.log('🔄 Switching to compatibility tab, checking data...');
|
|
|
|
|
|
|
|
|
if (this.mode === 'character' && this.strengths.length === 0 && this.risks.length === 0 && this.sacrifices.length === 0) {
|
|
|
console.log('🎯 Loading character data on tab switch...');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
console.log('📊 Current character data state:', {
|
|
|
strengths: this.strengths,
|
|
|
risks: this.risks,
|
|
|
sacrifices: this.sacrifices,
|
|
|
profileExplanations: this.selectedProfile.explanations
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
private marriageProfiles: Map<number, any> = new Map();
|
|
|
private userPhotoMap: Map<number, string> = new Map();
|
|
|
private usedPhotos: Set<string> = new Set();
|
|
|
|
|
|
|
|
|
private usedPhotoIndices: Set<number> = new Set();
|
|
|
private lastUsedMaleIndex: number = -1;
|
|
|
private lastUsedFemaleIndex: number = -1;
|
|
|
private recentMaleIndices: number[] = [];
|
|
|
private recentFemaleIndices: number[] = [];
|
|
|
private readonly MAX_RECENT_INDICES = 8;
|
|
|
|
|
|
private readonly indianMalePhotos: string[] = [
|
|
|
'assets/men/1.png',
|
|
|
'assets/men/2.png',
|
|
|
'assets/men/3.png',
|
|
|
'assets/men/4.png',
|
|
|
'assets/men/5.png',
|
|
|
'assets/men/6.png',
|
|
|
'assets/men/7.png',
|
|
|
'assets/men/8.png',
|
|
|
'assets/men/9.png',
|
|
|
'assets/men/10.png',
|
|
|
'assets/men/11.png',
|
|
|
'assets/men/12.png',
|
|
|
'assets/men/13.png',
|
|
|
'assets/men/14.png',
|
|
|
'assets/men/15.png',
|
|
|
'assets/men/16.png',
|
|
|
'assets/men/17.png',
|
|
|
'assets/men/18.png',
|
|
|
'assets/men/19.png',
|
|
|
'assets/men/20.png',
|
|
|
'assets/men/21.png',
|
|
|
'assets/men/22.png',
|
|
|
'assets/men/23.png',
|
|
|
'assets/men/24.png',
|
|
|
'assets/men/25.png',
|
|
|
'assets/men/26.png',
|
|
|
'assets/men/27.png',
|
|
|
'assets/men/28.png',
|
|
|
'assets/men/29.png',
|
|
|
'assets/men/30.png',
|
|
|
'assets/men/31.png',
|
|
|
'assets/men/32.png',
|
|
|
'assets/men/33.png',
|
|
|
'assets/men/34.png',
|
|
|
'assets/men/35.png',
|
|
|
'assets/men/36.png',
|
|
|
'assets/men/37.png',
|
|
|
'assets/men/38.png',
|
|
|
'assets/men/39.png',
|
|
|
'assets/men/40.png'
|
|
|
]
|
|
|
|
|
|
private readonly indianFemalePhotos: string[] = [
|
|
|
'assets/women/1.png',
|
|
|
'assets/women/2.png',
|
|
|
'assets/women/3.png',
|
|
|
'assets/women/4.png',
|
|
|
'assets/women/5.png',
|
|
|
'assets/women/6.png',
|
|
|
'assets/women/7.png',
|
|
|
'assets/women/8.png',
|
|
|
'assets/women/9.png',
|
|
|
'assets/women/10.png',
|
|
|
'assets/women/11.png',
|
|
|
'assets/women/12.png',
|
|
|
'assets/women/13.png',
|
|
|
'assets/women/14.png',
|
|
|
'assets/women/15.png',
|
|
|
'assets/women/16.png',
|
|
|
'assets/women/17.png',
|
|
|
'assets/women/18.png',
|
|
|
'assets/women/19.png',
|
|
|
'assets/women/20.png',
|
|
|
'assets/women/21.png',
|
|
|
'assets/women/22.png',
|
|
|
'assets/women/23.png',
|
|
|
'assets/women/24.png',
|
|
|
'assets/women/25.png',
|
|
|
'assets/women/26.png',
|
|
|
'assets/women/27.png',
|
|
|
'assets/women/28.png',
|
|
|
'assets/women/29.png',
|
|
|
'assets/women/30.png',
|
|
|
'assets/women/31.png',
|
|
|
'assets/women/32.png',
|
|
|
'assets/women/33.png',
|
|
|
'assets/women/34.png',
|
|
|
'assets/women/35.png',
|
|
|
'assets/women/36.png',
|
|
|
'assets/women/37.png',
|
|
|
'assets/women/38.png',
|
|
|
'assets/women/39.png',
|
|
|
'assets/women/40.png'
|
|
|
];
|
|
|
|
|
|
constructor(
|
|
|
private matchingService: MatchingService,
|
|
|
private route: ActivatedRoute,
|
|
|
private router: Router
|
|
|
) { }
|
|
|
|
|
|
onDocumentClick(event: MouseEvent): void {
|
|
|
const likeSection = document.querySelector('.like-section');
|
|
|
if (likeSection && !likeSection.contains(event.target as Node)) {
|
|
|
this.showLikedDropdown = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
ngOnDestroy(): void {
|
|
|
document.removeEventListener('click', this.onDocumentClick.bind(this));
|
|
|
}
|
|
|
|
|
|
ngOnInit(): void {
|
|
|
this.route.paramMap.subscribe((params) => {
|
|
|
this.processUserId(params);
|
|
|
});
|
|
|
|
|
|
this.route.queryParamMap.subscribe((queryParams) => {
|
|
|
this.processUserId(queryParams);
|
|
|
});
|
|
|
document.addEventListener('click', this.onDocumentClick.bind(this));
|
|
|
}
|
|
|
|
|
|
private processUserId(paramMap: any): void {
|
|
|
const idFromId = Number(paramMap.get('id'));
|
|
|
const idFromUserId = Number(paramMap.get('user_id'));
|
|
|
|
|
|
this.userId = !isNaN(idFromId) && idFromId > 0 ? idFromId :
|
|
|
!isNaN(idFromUserId) && idFromUserId > 0 ? idFromUserId : null;
|
|
|
|
|
|
if (this.userId && !this.loading) {
|
|
|
this.findMatches(this.userId);
|
|
|
this.loadUserProfile(this.userId);
|
|
|
} else if (!this.userId && !this.loading) {
|
|
|
this.error = 'No user ID available. Please provide a user ID.';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
loadUserProfile(userId: number): void {
|
|
|
this.loading = true;
|
|
|
|
|
|
this.matchingService.getMarriageProfile(userId).subscribe({
|
|
|
next: (profile: any) => {
|
|
|
if (profile) {
|
|
|
this.userName = profile.full_name || 'User Name';
|
|
|
this.userCity = profile.current_city || 'City';
|
|
|
if (profile.gender) {
|
|
|
this.userPhoto = this.getConsistentPhoto(userId, profile.gender);
|
|
|
}
|
|
|
}
|
|
|
this.loading = false;
|
|
|
},
|
|
|
error: (err: any) => {
|
|
|
console.error('Failed to load user profile:', err);
|
|
|
this.userName = 'User Name';
|
|
|
this.userCity = 'City';
|
|
|
this.loading = false;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
findMatches(userId: number): void {
|
|
|
this.error = null;
|
|
|
this.result = null;
|
|
|
this.groupedMatches = [];
|
|
|
this.displayedMatches = [];
|
|
|
this.loading = true;
|
|
|
|
|
|
this.matchingService.checkMandatoryFields(userId).subscribe({
|
|
|
next: (mandatoryResult) => {
|
|
|
console.log('🎯 CURRENT MANDATORY FIELDS FOR MATCHING:', mandatoryResult);
|
|
|
},
|
|
|
error: (err) => {
|
|
|
console.error('❌ Failed to check mandatory fields:', err);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
this.matchingService
|
|
|
|
|
|
.getMatches(userId, {
|
|
|
role: 'marriage',
|
|
|
limit: 10,
|
|
|
excludeSelf: true,
|
|
|
mode: this.mode
|
|
|
})
|
|
|
.subscribe({
|
|
|
next: (res: ApiMatchResponse) => {
|
|
|
this.processMatches(res, this.mode);
|
|
|
},
|
|
|
error: (err: any) => {
|
|
|
console.error('❌ ERROR in match finding:', err);
|
|
|
this.error = err?.error?.error || 'Failed to fetch matches.';
|
|
|
this.loading = false;
|
|
|
},
|
|
|
complete: () => {
|
|
|
this.loading = false;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
sortByExpectationOnly(): void {
|
|
|
if (!this.userId) return;
|
|
|
this.loading = true;
|
|
|
this.error = null;
|
|
|
this.mode = 'expectation-only';
|
|
|
|
|
|
this.matchingService
|
|
|
.getMatches(this.userId, {
|
|
|
role: 'marriage',
|
|
|
limit: 10,
|
|
|
excludeSelf: true,
|
|
|
mode: 'expectation-only'
|
|
|
})
|
|
|
.subscribe({
|
|
|
next: (res: ApiMatchResponse) => {
|
|
|
this.processMatches(res, 'expectation-only');
|
|
|
|
|
|
setTimeout(() => this.applyFilters(), 0);
|
|
|
},
|
|
|
error: (err: any) => {
|
|
|
this.error = err?.error?.error || 'Failed to fetch expectation matches.';
|
|
|
this.loading = false;
|
|
|
},
|
|
|
complete: () => (this.loading = false),
|
|
|
});
|
|
|
}
|
|
|
|
|
|
sortByExpectation(): void {
|
|
|
if (!this.userId) return;
|
|
|
this.loading = true;
|
|
|
this.error = null;
|
|
|
this.mode = 'expectation';
|
|
|
|
|
|
this.matchingService
|
|
|
.getMatches(this.userId, {
|
|
|
role: 'marriage',
|
|
|
limit: 10,
|
|
|
excludeSelf: true,
|
|
|
mode: 'expectation'
|
|
|
})
|
|
|
.subscribe({
|
|
|
next: (res: ApiMatchResponse) => {
|
|
|
this.processMatches(res, 'expectation');
|
|
|
|
|
|
setTimeout(() => this.applyFilters(), 0);
|
|
|
},
|
|
|
error: (err: any) => {
|
|
|
this.error = err?.error?.error || 'Failed to fetch matches.';
|
|
|
this.loading = false;
|
|
|
},
|
|
|
complete: () => (this.loading = false),
|
|
|
});
|
|
|
}
|
|
|
|
|
|
sortCharacterwise(): void {
|
|
|
if (!this.userId) return;
|
|
|
this.loading = true;
|
|
|
this.error = null;
|
|
|
this.mode = 'character';
|
|
|
|
|
|
this.matchingService
|
|
|
.getMatches(this.userId, {
|
|
|
role: 'marriage',
|
|
|
limit: 10,
|
|
|
excludeSelf: true,
|
|
|
mode: 'character'
|
|
|
})
|
|
|
.subscribe({
|
|
|
next: (res: ApiMatchResponse) => {
|
|
|
this.processMatches(res, 'character');
|
|
|
|
|
|
setTimeout(() => this.applyFilters(), 0);
|
|
|
},
|
|
|
error: (err: any) => {
|
|
|
this.error = err?.error?.error || 'Failed to fetch character matches.';
|
|
|
this.loading = false;
|
|
|
},
|
|
|
complete: () => (this.loading = false),
|
|
|
});
|
|
|
}
|
|
|
|
|
|
private processMatches(res: ApiMatchResponse, mode: string): void {
|
|
|
console.log(`🎯 Processing matches for mode: ${mode}`);
|
|
|
|
|
|
this.result = res;
|
|
|
|
|
|
let scoreKey: keyof Pick<ApiMatchItem, 'score_color' | 'final_score' | 'score_expect'>;
|
|
|
if (mode === 'expectation-only') {
|
|
|
scoreKey = 'score_expect';
|
|
|
} else if (mode === 'character') {
|
|
|
scoreKey = 'score_color';
|
|
|
} else {
|
|
|
scoreKey = 'final_score';
|
|
|
}
|
|
|
|
|
|
this.groupedMatches = this.prepareGroups(res.matches, scoreKey);
|
|
|
this.totalMatches = this.calculateTotalMatches(res.matches);
|
|
|
|
|
|
this.loadMarriageProfiles().then(() => {
|
|
|
this.processMatchesForDisplay();
|
|
|
console.log(`✅ ${mode} match processing completed`);
|
|
|
});
|
|
|
|
|
|
this.mode = mode as 'expectation-only' | 'expectation' | 'character';
|
|
|
}
|
|
|
|
|
|
private async loadMarriageProfiles(): Promise<void> {
|
|
|
if (!this.result) return;
|
|
|
|
|
|
const userIds: number[] = [];
|
|
|
this.groupedMatches.forEach(group => {
|
|
|
group.items.forEach(item => {
|
|
|
userIds.push(item.user_id);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
for (const userId of userIds) {
|
|
|
if (!this.marriageProfiles.has(userId)) {
|
|
|
try {
|
|
|
const profile = await this.fetchMarriageProfile(userId);
|
|
|
if (profile) {
|
|
|
this.marriageProfiles.set(userId, profile);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error(`Failed to fetch profile for user ${userId}:`, error);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private fetchMarriageProfile(userId: number): Promise<any> {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
this.matchingService.getMarriageProfile(userId).subscribe({
|
|
|
next: (profile: any) => resolve(profile),
|
|
|
error: (err: any) => reject(err)
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
public processMatchesForDisplay(): void {
|
|
|
this.resetPhotoTracking();
|
|
|
|
|
|
const locations = new Set<string>();
|
|
|
const professions = new Set<string>();
|
|
|
const educationLevels = new Set<string>();
|
|
|
const religions = new Set<string>();
|
|
|
|
|
|
this.displayedMatches = [];
|
|
|
const filteredGroups = this.filteredGroups;
|
|
|
|
|
|
filteredGroups.forEach(group => {
|
|
|
group.items.forEach(item => {
|
|
|
const marriageProfile = this.marriageProfiles.get(item.user_id);
|
|
|
|
|
|
let age: number | undefined;
|
|
|
if (marriageProfile?.date_of_birth) {
|
|
|
age = this.calculateAge(marriageProfile.date_of_birth);
|
|
|
}
|
|
|
|
|
|
const gender = this.getGender(item, marriageProfile);
|
|
|
|
|
|
let matchScore: number;
|
|
|
let expectationScore: number;
|
|
|
let characterScore: number;
|
|
|
|
|
|
if (this.mode === 'expectation-only') {
|
|
|
matchScore = Math.round((item.score_expect || 0) * 100);
|
|
|
expectationScore = matchScore;
|
|
|
characterScore = Math.round((item.score_color || 0) * 100);
|
|
|
} else if (this.mode === 'character') {
|
|
|
matchScore = Math.round((item.score_color || 0) * 100);
|
|
|
characterScore = matchScore;
|
|
|
expectationScore = Math.round((item.score_expect || 0) * 100);
|
|
|
} else {
|
|
|
matchScore = Math.round(item.final_score || 0);
|
|
|
expectationScore = Math.round((item.score_expect || 0) * 100);
|
|
|
characterScore = Math.round((item.score_color || 0) * 100);
|
|
|
}
|
|
|
|
|
|
|
|
|
if (marriageProfile?.current_city) {
|
|
|
locations.add(marriageProfile.current_city);
|
|
|
}
|
|
|
if (marriageProfile?.job_role) {
|
|
|
professions.add(marriageProfile.job_role);
|
|
|
}
|
|
|
if (marriageProfile?.education_level) {
|
|
|
educationLevels.add(marriageProfile.education_level);
|
|
|
}
|
|
|
if (marriageProfile?.religion) {
|
|
|
religions.add(marriageProfile.religion);
|
|
|
}
|
|
|
|
|
|
const profile: Profile = {
|
|
|
user_id: item.user_id,
|
|
|
name: item.name || 'Unknown',
|
|
|
age: age,
|
|
|
city: marriageProfile?.current_city || 'Unknown',
|
|
|
job: marriageProfile?.job_role || marriageProfile?.employment_status || 'Not specified',
|
|
|
photo_url: this.getConsistentPhoto(item.user_id, gender),
|
|
|
isOnline: Math.random() > 0.5,
|
|
|
match_score: matchScore,
|
|
|
score: matchScore,
|
|
|
gender: gender,
|
|
|
blue: item.blue,
|
|
|
green: item.green,
|
|
|
yellow: item.yellow,
|
|
|
red: item.red,
|
|
|
explanations: item.explanations,
|
|
|
explanation_source: item.explanation_source,
|
|
|
|
|
|
|
|
|
current_city: marriageProfile?.current_city,
|
|
|
date_of_birth: marriageProfile?.date_of_birth,
|
|
|
job_role: marriageProfile?.job_role,
|
|
|
employment_status: marriageProfile?.employment_status,
|
|
|
marital_status: marriageProfile?.marital_status,
|
|
|
education_level: marriageProfile?.education_level,
|
|
|
full_name: marriageProfile?.full_name,
|
|
|
height: marriageProfile?.height,
|
|
|
skin_tone: marriageProfile?.skin_tone,
|
|
|
languages_spoken: marriageProfile?.languages_spoken,
|
|
|
country: marriageProfile?.country,
|
|
|
blood_group: marriageProfile?.blood_group,
|
|
|
religion: marriageProfile?.religion,
|
|
|
dual_citizenship: marriageProfile?.dual_citizenship,
|
|
|
number_of_siblings: marriageProfile?.number_of_siblings,
|
|
|
siblings_position: marriageProfile?.siblings_position,
|
|
|
parents_living_status: marriageProfile?.parents_living_status,
|
|
|
live_with_parents: marriageProfile?.live_with_parents,
|
|
|
support_parents_financially: marriageProfile?.support_parents_financially,
|
|
|
family_communication_frequency: marriageProfile?.family_communication_frequency,
|
|
|
food_preference: marriageProfile?.food_preference,
|
|
|
smoking_habit: marriageProfile?.smoking_habit,
|
|
|
alcohol_habit: marriageProfile?.alcohol_habit,
|
|
|
daily_routine: marriageProfile?.daily_routine,
|
|
|
fitness_level: marriageProfile?.fitness_level,
|
|
|
own_pets: marriageProfile?.own_pets,
|
|
|
travel_preference: marriageProfile?.travel_preference,
|
|
|
relaxation_mode: marriageProfile?.relaxation_mode,
|
|
|
work_experience_years: marriageProfile?.work_experience_years,
|
|
|
career_aspirations: marriageProfile?.career_aspirations,
|
|
|
field_of_study: marriageProfile?.field_of_study,
|
|
|
remark: marriageProfile?.remark,
|
|
|
hobbies_interests: marriageProfile?.hobbies_interests,
|
|
|
conflict_approach: marriageProfile?.conflict_approach,
|
|
|
financial_style: marriageProfile?.financial_style,
|
|
|
income_range: marriageProfile?.income_range,
|
|
|
relocation_willingness: marriageProfile?.relocation_willingness,
|
|
|
family_type: marriageProfile?.family_type,
|
|
|
children_timeline: marriageProfile?.children_timeline,
|
|
|
open_to_adoption: marriageProfile?.open_to_adoption,
|
|
|
deal_breakers: marriageProfile?.deal_breakers,
|
|
|
other_non_negotiables: marriageProfile?.other_non_negotiables,
|
|
|
health_constraints: marriageProfile?.health_constraints,
|
|
|
live_with_inlaws: marriageProfile?.live_with_inlaws,
|
|
|
|
|
|
expectation_score: expectationScore,
|
|
|
character_score: characterScore
|
|
|
};
|
|
|
|
|
|
this.displayedMatches.push(profile);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
|
|
|
this.availableLocations = Array.from(locations).sort();
|
|
|
this.availableProfessions = Array.from(professions).sort();
|
|
|
this.availableEducationLevels = Array.from(educationLevels).sort();
|
|
|
this.availableReligions = Array.from(religions).sort();
|
|
|
|
|
|
this.displayedMatches.sort((a, b) => (b.match_score || 0) - (a.match_score || 0));
|
|
|
}
|
|
|
|
|
|
private getGender(item: ApiMatchItem, marriageProfile: any): string {
|
|
|
if (item.gender) return item.gender;
|
|
|
if (marriageProfile?.gender) return marriageProfile.gender;
|
|
|
return Math.random() > 0.5 ? 'male' : 'female';
|
|
|
}
|
|
|
|
|
|
private calculateAge(dateOfBirth: string): number {
|
|
|
try {
|
|
|
const birthDate = new Date(dateOfBirth);
|
|
|
const today = new Date();
|
|
|
let age = today.getFullYear() - birthDate.getFullYear();
|
|
|
const monthDiff = today.getMonth() - birthDate.getMonth();
|
|
|
|
|
|
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
|
|
|
age--;
|
|
|
}
|
|
|
return age;
|
|
|
} catch (error) {
|
|
|
console.error('Error calculating age:', error);
|
|
|
return Math.floor(Math.random() * 20) + 25;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private calculateTotalMatches(matches: { [range: string]: ApiMatchItem[] }): number {
|
|
|
return Object.values(matches).reduce((total, group) => total + group.length, 0);
|
|
|
}
|
|
|
|
|
|
private prepareGroups(matches: ApiMatchResponse['matches'], sortKey: keyof Pick<ApiMatchItem, 'score_color' | 'final_score' | 'score_expect'>) {
|
|
|
const ordered: { range: string; items: ApiMatchItem[] }[] = [];
|
|
|
const order = ['90-100', '80-89', '70-79', '60-69', 'below_60'];
|
|
|
|
|
|
for (const range of Object.keys(matches)) {
|
|
|
const items = matches[range] || [];
|
|
|
ordered.push({
|
|
|
range,
|
|
|
items: [...items].sort((a, b) => (Number(b[sortKey]) || 0) - (Number(a[sortKey]) || 0)),
|
|
|
});
|
|
|
}
|
|
|
|
|
|
ordered.sort((a, b) => order.indexOf(a.range) - order.indexOf(b.range));
|
|
|
return ordered;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fetchCompatibilityExplanation(profile: Profile): void {
|
|
|
if (!this.userId) return;
|
|
|
|
|
|
|
|
|
if (this.characterExplanationLoaded) {
|
|
|
console.log(⏭️ Character explanation already loaded, skipping API call');
|
|
|
return;
|
|
|
}
|
|
|
this.characterExplanationLoaded = true;
|
|
|
|
|
|
this.matchingService.getCompatibilityExplanation(
|
|
|
this.userId,
|
|
|
profile.user_id,
|
|
|
this.mode
|
|
|
).subscribe({
|
|
|
next: (response) => {
|
|
|
const updatedProfile = this.displayedMatches.find(p => p.user_id === profile.user_id);
|
|
|
if (updatedProfile) {
|
|
|
|
|
|
// ✅ Store LLM or Expectation explanation exactly as backend sends
|
|
|
updatedProfile.explanations = response.explanations;
|
|
|
|
|
|
// ✅ MOST IMPORTANT FIX
|
|
|
updatedProfile.explanation_source = response.source;
|
|
|
// ✅ Insert this line here
|
|
|
this.processCharacterExplanation(response.explanations);
|
|
|
}
|
|
|
},
|
|
|
error: (err) => {
|
|
|
console.error('Failed to fetch compatibility explanation:', err);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
processCharacterExplanation(explanations: string[]) {
|
|
|
console.log('🔍 Frontend: RAW explanations from backend:', explanations);
|
|
|
this.characterExplanationLoaded = false; // allow retry on failure
|
|
|
|
|
|
this.strengths = [];
|
|
|
this.risks = [];
|
|
|
this.sacrifices = [];
|
|
|
|
|
|
let currentSection = '';
|
|
|
|
|
|
explanations.forEach((line, index) => {
|
|
|
const trimmedLine = line.trim();
|
|
|
if (!trimmedLine) return;
|
|
|
|
|
|
console.log(`🔍 Frontend: Line ${index}: "${trimmedLine}"`);
|
|
|
|
|
|
// Clean the line - remove markdown and other formatting
|
|
|
const cleanLine = trimmedLine
|
|
|
.replace(/^[-•*]\s*/, '') // Remove bullet points
|
|
|
.replace(/^#+\s*/, '') // Remove markdown headers
|
|
|
.replace(/\*\*(.*?)\*\*/g, '$1') // Remove bold formatting
|
|
|
.trim();
|
|
|
|
|
|
// Skip if line is too short after cleaning
|
|
|
if (cleanLine.length < 5) return;
|
|
|
|
|
|
// Section detection - more flexible with duplicate prevention
|
|
|
const lowerLine = cleanLine.toLowerCase();
|
|
|
const isSectionHeader = this.isSectionHeader(cleanLine);
|
|
|
|
|
|
if (isSectionHeader) {
|
|
|
// Only switch section if it's actually a different section
|
|
|
if (lowerLine.includes('strength') && currentSection !== 'strengths') {
|
|
|
currentSection = 'strengths';
|
|
|
console.log('🎯 Frontend: Switching to strengths section');
|
|
|
return;
|
|
|
}
|
|
|
else if (lowerLine.includes('risk') && currentSection !== 'risks') {
|
|
|
currentSection = 'risks';
|
|
|
console.log('🎯 Frontend: Switching to risks section');
|
|
|
return;
|
|
|
}
|
|
|
else if (lowerLine.includes('sacrific') && currentSection !== 'sacrifices') {
|
|
|
currentSection = 'sacrifices';
|
|
|
console.log('🎯 Frontend: Switching to sacrifices section');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
else if ((lowerLine.startsWith('1.') || lowerLine.includes('1)')) && currentSection !== 'strengths') {
|
|
|
currentSection = 'strengths';
|
|
|
console.log('🎯 Frontend: Switching to strengths section (numbered)');
|
|
|
return;
|
|
|
}
|
|
|
else if ((lowerLine.startsWith('2.') || lowerLine.includes('2)')) && currentSection !== 'risks') {
|
|
|
currentSection = 'risks';
|
|
|
console.log('🎯 Frontend: Switching to risks section (numbered)');
|
|
|
return;
|
|
|
}
|
|
|
else if ((lowerLine.startsWith('3.') || lowerLine.includes('3)')) && currentSection !== 'sacrifices') {
|
|
|
currentSection = 'sacrifices';
|
|
|
console.log('🎯 Frontend: Switching to sacrifices section (numbered)');
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
if (currentSection && cleanLine.length > 10 && !isSectionHeader) {
|
|
|
if (currentSection === 'strengths') {
|
|
|
this.strengths.push(cleanLine);
|
|
|
console.log('✅ Frontend: Added to strengths:', cleanLine);
|
|
|
} else if (currentSection === 'risks') {
|
|
|
this.risks.push(cleanLine);
|
|
|
console.log('✅ Frontend: Added to risks:', cleanLine);
|
|
|
} else if (currentSection === 'sacrifices') {
|
|
|
this.sacrifices.push(cleanLine);
|
|
|
console.log('✅ Frontend: Added to sacrifices:', cleanLine);
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
if (this.strengths.length === 0 && this.risks.length === 0 && this.sacrifices.length === 0) {
|
|
|
console.log('🔄 Frontend: No sections detected, using fallback distribution');
|
|
|
this.distributeContentToSections(explanations);
|
|
|
}
|
|
|
|
|
|
|
|
|
this.ensureMinimumContent();
|
|
|
|
|
|
|
|
|
this.splitLongContent();
|
|
|
|
|
|
console.log('📊 Frontend: Final processed sections', {
|
|
|
strengths: this.strengths,
|
|
|
risks: this.risks,
|
|
|
sacrifices: this.sacrifices
|
|
|
});
|
|
|
}
|
|
|
|
|
|
private isSectionHeader(line: string): boolean {
|
|
|
const lowerLine = line.toLowerCase();
|
|
|
return lowerLine.includes('character strength') ||
|
|
|
lowerLine.includes('character risk') ||
|
|
|
lowerLine.includes('sacrifices needed') ||
|
|
|
lowerLine.includes('1. character') ||
|
|
|
lowerLine.includes('2. character') ||
|
|
|
lowerLine.includes('3. sacrifices') ||
|
|
|
lowerLine === 'character strengths' ||
|
|
|
lowerLine === 'character risks' ||
|
|
|
lowerLine === 'sacrifices needed' ||
|
|
|
lowerLine === '1. character strengths' ||
|
|
|
lowerLine === '2. character risks' ||
|
|
|
lowerLine === '3. sacrifices needed';
|
|
|
}
|
|
|
|
|
|
private splitLongContent() {
|
|
|
|
|
|
const splitLongString = (text: string, maxLength: number = 150): string[] => {
|
|
|
if (text.length <= maxLength) return [text];
|
|
|
|
|
|
const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0);
|
|
|
const result: string[] = [];
|
|
|
let currentChunk = '';
|
|
|
|
|
|
for (const sentence of sentences) {
|
|
|
const trimmedSentence = sentence.trim();
|
|
|
if (!trimmedSentence) continue;
|
|
|
|
|
|
if (currentChunk.length + trimmedSentence.length + 2 <= maxLength) {
|
|
|
currentChunk += (currentChunk ? '. ' : '') + trimmedSentence;
|
|
|
} else {
|
|
|
if (currentChunk) {
|
|
|
result.push(currentChunk + '.');
|
|
|
}
|
|
|
currentChunk = trimmedSentence;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (currentChunk) {
|
|
|
result.push(currentChunk + '.');
|
|
|
}
|
|
|
|
|
|
return result.length > 0 ? result : [text];
|
|
|
};
|
|
|
|
|
|
this.strengths = this.strengths.flatMap(item => splitLongString(item));
|
|
|
this.risks = this.risks.flatMap(item => splitLongString(item));
|
|
|
this.sacrifices = this.sacrifices.flatMap(item => splitLongString(item));
|
|
|
}
|
|
|
|
|
|
private distributeContentToSections(lines: string[]) {
|
|
|
const substantiveLines = lines
|
|
|
.map(line => line
|
|
|
.replace(/^[-•*]\s*/, '')
|
|
|
.replace(/^#+\s*/, '')
|
|
|
.replace(/\*\*(.*?)\*\*/g, '$1')
|
|
|
.trim()
|
|
|
)
|
|
|
.filter(line =>
|
|
|
line.length > 10 &&
|
|
|
!this.isSectionHeader(line) &&
|
|
|
!line.toLowerCase().includes('compatibility') &&
|
|
|
!line.includes('---')
|
|
|
);
|
|
|
|
|
|
if (substantiveLines.length === 0) return;
|
|
|
|
|
|
|
|
|
const sectionSize = Math.ceil(substantiveLines.length / 3);
|
|
|
|
|
|
this.strengths = substantiveLines.slice(0, sectionSize);
|
|
|
this.risks = substantiveLines.slice(sectionSize, sectionSize * 2);
|
|
|
this.sacrifices = substantiveLines.slice(sectionSize * 2);
|
|
|
}
|
|
|
|
|
|
private ensureMinimumContent() {
|
|
|
|
|
|
const totalItems = this.strengths.length + this.risks.length + this.sacrifices.length;
|
|
|
|
|
|
if (totalItems > 0) {
|
|
|
if (this.strengths.length === 0 && this.risks.length > 1) {
|
|
|
this.strengths.push(this.risks.pop()!);
|
|
|
}
|
|
|
if (this.risks.length === 0 && this.sacrifices.length > 1) {
|
|
|
this.risks.push(this.sacrifices.pop()!);
|
|
|
}
|
|
|
if (this.sacrifices.length === 0 && this.strengths.length > 1) {
|
|
|
this.sacrifices.push(this.strengths.pop()!);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
if (totalItems >= 3) {
|
|
|
while (this.strengths.length === 0 && (this.risks.length > 1 || this.sacrifices.length > 1)) {
|
|
|
if (this.risks.length > 1) {
|
|
|
this.strengths.push(this.risks.pop()!);
|
|
|
} else if (this.sacrifices.length > 1) {
|
|
|
this.strengths.push(this.sacrifices.pop()!);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
while (this.risks.length === 0 && (this.strengths.length > 1 || this.sacrifices.length > 1)) {
|
|
|
if (this.strengths.length > 1) {
|
|
|
this.risks.push(this.strengths.pop()!);
|
|
|
} else if (this.sacrifices.length > 1) {
|
|
|
this.risks.push(this.sacrifices.pop()!);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
while (this.sacrifices.length === 0 && (this.strengths.length > 1 || this.risks.length > 1)) {
|
|
|
if (this.strengths.length > 1) {
|
|
|
this.sacrifices.push(this.strengths.pop()!);
|
|
|
} else if (this.risks.length > 1) {
|
|
|
this.sacrifices.push(this.risks.pop()!);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
openProfileModal(profile: Profile): void {
|
|
|
this.selectedProfile = profile;
|
|
|
this.showProfileModal = true;
|
|
|
this.showExactMatches = true;
|
|
|
this.showDifferences = false;
|
|
|
this.compatibilityAnalysis = null;
|
|
|
this.activeTab = 'details';
|
|
|
|
|
|
this.characterExplanationLoaded = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (this.mode === 'expectation') {
|
|
|
this.subTab = 'expectation';
|
|
|
} else {
|
|
|
this.subTab = 'expectation';
|
|
|
}
|
|
|
|
|
|
console.log('🟢 Opening profile modal for:', profile.name, 'Mode:', this.mode);
|
|
|
|
|
|
|
|
|
this.strengths = [];
|
|
|
this.risks = [];
|
|
|
this.sacrifices = [];
|
|
|
|
|
|
|
|
|
if (this.mode === 'expectation-only') {
|
|
|
console.log('🎯 Loading expectation-only analysis...');
|
|
|
this.loadCompatibilityAnalysis(profile);
|
|
|
}
|
|
|
else if (this.mode === 'character') {
|
|
|
console.log('🎯 Loading character analysis...');
|
|
|
this.fetchCompatibilityExplanation(profile);
|
|
|
}
|
|
|
else if (this.mode === 'expectation') {
|
|
|
console.log('🎯 Loading both expectation and character analysis...');
|
|
|
|
|
|
this.loadCompatibilityAnalysis(profile);
|
|
|
this.fetchCompatibilityExplanation(profile);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private loadCompatibilityAnalysis(profile: Profile): void {
|
|
|
if (!this.userId) return;
|
|
|
|
|
|
console.log('🟢 loadCompatibilityAnalysis called for mode:', this.mode);
|
|
|
|
|
|
|
|
|
if (this.mode === 'character') {
|
|
|
console.log('🎯 Character mode - fetching character explanation');
|
|
|
this.fetchCompatibilityExplanation(profile);
|
|
|
}
|
|
|
|
|
|
else if (this.mode === 'expectation-only' || this.mode === 'expectation') {
|
|
|
this.matchingService.getCompatibilityExplanation(
|
|
|
this.userId,
|
|
|
profile.user_id,
|
|
|
this.mode
|
|
|
).subscribe({
|
|
|
next: (response) => {
|
|
|
console.log('✅ Compatibility analysis received:', response);
|
|
|
this.processCompatibilityAnalysis(response.explanations);
|
|
|
},
|
|
|
error: (err) => {
|
|
|
console.error('❌ Failed to fetch compatibility analysis:', err);
|
|
|
this.generateExpectationFallbackAnalysis(profile);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
private generateCharacterStrengths(dominant: any, secondary: any): string[] {
|
|
|
const combinations: { [key: string]: string[] } = {
|
|
|
'Blue-Red': [
|
|
|
'Analytical thinking complements decisive action',
|
|
|
'Thorough planning balances quick decision-making',
|
|
|
'Data-driven approach supports confident leadership',
|
|
|
'Strategic thinking combined with strong execution'
|
|
|
],
|
|
|
'Green-Yellow': [
|
|
|
'Structured organization grounds creative ideas',
|
|
|
'Process-oriented approach gives vision practical form',
|
|
|
'Reliability provides stability for innovation',
|
|
|
'Methodical planning supports imaginative solutions'
|
|
|
],
|
|
|
'Blue-Green': [
|
|
|
'Detailed analysis combines with systematic execution',
|
|
|
'Methodical approach ensures thorough implementation',
|
|
|
'Precision and organization create reliable outcomes',
|
|
|
'Careful planning meets structured follow-through'
|
|
|
],
|
|
|
'Red-Yellow': [
|
|
|
'Action-oriented drive brings creative ideas to life',
|
|
|
'Bold decisions support visionary thinking',
|
|
|
'Energy and enthusiasm fuel innovative projects',
|
|
|
'Confident leadership inspires creative collaboration'
|
|
|
],
|
|
|
'Blue-Yellow': [
|
|
|
'Analytical depth enhances creative problem-solving',
|
|
|
'Thorough research supports innovative approaches',
|
|
|
'Logical thinking balances imaginative ideas',
|
|
|
'Data insights inspire creative solutions'
|
|
|
],
|
|
|
'Green-Red': [
|
|
|
'Organized planning directs decisive action',
|
|
|
'Systematic approach channels energetic drive',
|
|
|
'Process efficiency supports quick implementation',
|
|
|
'Reliable structure guides confident decisions'
|
|
|
]
|
|
|
};
|
|
|
|
|
|
const key = `${dominant.name}-${secondary.name}`;
|
|
|
return combinations[key] || [
|
|
|
'Complementary personality traits create balance',
|
|
|
'Different approaches bring diverse perspectives',
|
|
|
'Varied strengths cover multiple relationship aspects',
|
|
|
'Unique qualities enhance mutual understanding'
|
|
|
];
|
|
|
}
|
|
|
|
|
|
private generateCharacterRisks(dominant: any, secondary: any): string[] {
|
|
|
const combinations: { [key: string]: string[] } = {
|
|
|
'Blue-Red': [
|
|
|
'Over-analysis may frustrate action-oriented partner',
|
|
|
'Quick decisions might overlook important details',
|
|
|
'Direct communication may clash with thoughtful processing',
|
|
|
'Impatience with detailed planning could cause tension'
|
|
|
],
|
|
|
'Green-Yellow': [
|
|
|
'Rigid routines may limit spontaneous creativity',
|
|
|
'Unstructured ideas may disrupt organized systems',
|
|
|
'Process focus might slow down innovative thinking',
|
|
|
'Resistance to change could hinder creative expression'
|
|
|
],
|
|
|
'Blue-Green': [
|
|
|
'Excessive planning may delay actual progress',
|
|
|
'Over-caution might prevent necessary risks',
|
|
|
'Analysis paralysis in decision-making situations',
|
|
|
'Too much structure could limit flexibility'
|
|
|
],
|
|
|
'Red-Yellow': [
|
|
|
'Impulsive actions may lack long-term vision',
|
|
|
'Big ideas might overlook practical implementation',
|
|
|
'Enthusiasm may override careful consideration',
|
|
|
'Inconsistent follow-through on creative projects'
|
|
|
],
|
|
|
'Blue-Yellow': [
|
|
|
'Over-thinking may dampen spontaneous creativity',
|
|
|
'Abstract ideas might lack practical grounding',
|
|
|
'Detail focus could miss the bigger picture',
|
|
|
'Analysis may slow down creative momentum'
|
|
|
],
|
|
|
'Green-Red': [
|
|
|
'Bureaucratic processes may frustrate quick action',
|
|
|
'Impulsive decisions could disrupt careful planning',
|
|
|
'Directness may overwhelm methodical approach',
|
|
|
'Speed vs thoroughness conflict in projects'
|
|
|
]
|
|
|
};
|
|
|
|
|
|
const key = `${dominant.name}-${secondary.name}`;
|
|
|
return combinations[key] || [
|
|
|
'Different communication styles may cause misunderstandings',
|
|
|
'Varying energy levels could lead to timing conflicts',
|
|
|
'Contrasting approaches to problems may create tension',
|
|
|
'Differing priorities might require careful negotiation'
|
|
|
];
|
|
|
}
|
|
|
|
|
|
private generateCharacterSacrifices(dominant: any, secondary: any): string[] {
|
|
|
const combinations: { [key: string]: string[] } = {
|
|
|
'Blue-Red': [
|
|
|
'Analytical partner must accept quicker decisions sometimes',
|
|
|
'Action-oriented partner needs to allow time for reflection',
|
|
|
'Both must find middle ground between speed and thoroughness',
|
|
|
'Direct communicator should practice more patience in discussions'
|
|
|
],
|
|
|
'Green-Yellow': [
|
|
|
'Organized partner should embrace some spontaneity',
|
|
|
'Creative partner needs to respect established routines',
|
|
|
'Both must balance structure with flexibility',
|
|
|
'Process-focused partner should allow creative experimentation'
|
|
|
],
|
|
|
'Blue-Green': [
|
|
|
'Need to move from planning to action more quickly',
|
|
|
'Must embrace some uncertainty in decision-making',
|
|
|
'Both should practice more direct communication',
|
|
|
'Should balance perfectionism with practical progress'
|
|
|
],
|
|
|
'Red-Yellow': [
|
|
|
'Need to ground big ideas with practical steps',
|
|
|
'Must balance enthusiasm with realistic planning',
|
|
|
'Both should develop more patience in execution',
|
|
|
'Should combine vision with actionable strategies'
|
|
|
],
|
|
|
'Blue-Yellow': [
|
|
|
'Analytical thinker should embrace intuitive leaps',
|
|
|
'Creative partner needs to consider practical constraints',
|
|
|
'Both must balance imagination with reality checks',
|
|
|
'Should combine innovation with implementable plans'
|
|
|
],
|
|
|
'Green-Red': [
|
|
|
'Structured partner should allow faster execution sometimes',
|
|
|
'Action-oriented partner needs to follow established processes',
|
|
|
'Both must compromise between speed and quality',
|
|
|
'Should balance efficiency with thoroughness'
|
|
|
]
|
|
|
};
|
|
|
|
|
|
const key = `${dominant.name}-${secondary.name}`;
|
|
|
return combinations[key] || [
|
|
|
'Both partners need to understand different communication styles',
|
|
|
'Compromise between individual preferences and shared needs',
|
|
|
'Balance personal approaches with relationship harmony',
|
|
|
'Adapt to each other\'s natural rhythms and working styles'
|
|
|
];
|
|
|
}
|
|
|
private analyzeCharacterForSections(profile: Profile): {
|
|
|
strengths: string[],
|
|
|
risks: string[],
|
|
|
sacrifices: string[]
|
|
|
} {
|
|
|
const colors = [
|
|
|
{ name: 'Blue', value: profile.blue || 0, trait: 'analytical and structured' },
|
|
|
{ name: 'Green', value: profile.green || 0, trait: 'organized and process-oriented' },
|
|
|
{ name: 'Yellow', value: profile.yellow || 0, trait: 'creative and visionary' },
|
|
|
{ name: 'Red', value: profile.red || 0, trait: 'decisive and action-driven' }
|
|
|
];
|
|
|
|
|
|
|
|
|
const sortedColors = [...colors].sort((a, b) => b.value - a.value);
|
|
|
const dominant = sortedColors[0];
|
|
|
const secondary = sortedColors[1];
|
|
|
|
|
|
|
|
|
const strengths = this.generateCharacterStrengths(dominant, secondary);
|
|
|
const risks = this.generateCharacterRisks(dominant, secondary);
|
|
|
const sacrifices = this.generateCharacterSacrifices(dominant, secondary);
|
|
|
|
|
|
return { strengths, risks, sacrifices };
|
|
|
}
|
|
|
|
|
|
|
|
|
private generateCharacterFallbackAnalysis(profile: Profile): CompatibilityAnalysis {
|
|
|
const exactMatches: string[] = [];
|
|
|
const differences: FieldComparison[] = [];
|
|
|
const missingData: string[] = [];
|
|
|
|
|
|
|
|
|
const characterScore = profile.character_score || 0;
|
|
|
const characterAnalysis = this.analyzeCharacterForSections(profile);
|
|
|
|
|
|
|
|
|
characterAnalysis.strengths.slice(0, 3).forEach(strength => {
|
|
|
exactMatches.push(`✅ ${strength}`);
|
|
|
});
|
|
|
|
|
|
|
|
|
characterAnalysis.risks.slice(0, 2).forEach(risk => {
|
|
|
differences.push({
|
|
|
fieldName: 'Personality Dynamics',
|
|
|
yourExpectation: 'Harmonious interaction',
|
|
|
theirProfile: risk,
|
|
|
isCritical: false
|
|
|
});
|
|
|
});
|
|
|
|
|
|
|
|
|
characterAnalysis.sacrifices.slice(0, 2).forEach(sacrifice => {
|
|
|
missingData.push(`⚠️ ${sacrifice}`);
|
|
|
});
|
|
|
|
|
|
const totalComparisons = exactMatches.length + differences.length;
|
|
|
const compatibilityPercent = characterScore;
|
|
|
|
|
|
return {
|
|
|
exactMatches,
|
|
|
differences,
|
|
|
missingData,
|
|
|
compatibilityPercent,
|
|
|
totalExactMatches: exactMatches.length,
|
|
|
totalDifferences: differences.length,
|
|
|
totalMissingData: missingData.length
|
|
|
};
|
|
|
}
|
|
|
|
|
|
|
|
|
private getDetailedColorAnalysis(profile: Profile): {
|
|
|
strengths: {
|
|
|
calmEnergy: string;
|
|
|
decisionMaking: string;
|
|
|
emotionalPatterns: string;
|
|
|
};
|
|
|
risks: {
|
|
|
communicationStyle: string;
|
|
|
lifestylePace: string;
|
|
|
};
|
|
|
sacrifices: {
|
|
|
communication: string;
|
|
|
emotionalNeeds: string;
|
|
|
dailyRoutines: string;
|
|
|
};
|
|
|
} {
|
|
|
const colors = [
|
|
|
{ name: 'Blue', value: profile.blue || 0, traits: ['analytical', 'calm', 'thoughtful'] },
|
|
|
{ name: 'Green', value: profile.green || 0, traits: ['organized', 'structured', 'process-oriented'] },
|
|
|
{ name: 'Yellow', value: profile.yellow || 0, traits: ['creative', 'visionary', 'spontaneous'] },
|
|
|
{ name: 'Red', value: profile.red || 0, traits: ['decisive', 'action-driven', 'direct'] }
|
|
|
];
|
|
|
|
|
|
|
|
|
const dominantColors = [...colors].sort((a, b) => b.value - a.value);
|
|
|
const primary = dominantColors[0];
|
|
|
const secondary = dominantColors[1];
|
|
|
|
|
|
|
|
|
const strengths = this.generateStrengths(primary, secondary);
|
|
|
const risks = this.generateRisks(primary, secondary);
|
|
|
const sacrifices = this.generateSacrifices(primary, secondary);
|
|
|
|
|
|
return { strengths, risks, sacrifices };
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onRangeChange(): void {
|
|
|
this.applyFilters();
|
|
|
}
|
|
|
|
|
|
|
|
|
private generateStrengths(primary: any, secondary: any): any {
|
|
|
const strengthTemplates = [
|
|
|
"One brings calm thinking while the other adds energy, creating balance.",
|
|
|
"Their decisive nature pairs well with your thoughtful approach.",
|
|
|
"Both show complementary emotional patterns.",
|
|
|
"Practical planning meets creative problem-solving effectively.",
|
|
|
"Structured approach balances spontaneous decision-making.",
|
|
|
"Analytical thinking complements intuitive understanding."
|
|
|
];
|
|
|
|
|
|
|
|
|
const selectedStrengths = [
|
|
|
strengthTemplates[0],
|
|
|
this.getSpecificStrength(primary, secondary),
|
|
|
strengthTemplates[5]
|
|
|
];
|
|
|
|
|
|
return {
|
|
|
calmEnergy: selectedStrengths[0],
|
|
|
decisionMaking: selectedStrengths[1],
|
|
|
emotionalPatterns: selectedStrengths[2]
|
|
|
};
|
|
|
}
|
|
|
|
|
|
private getSpecificStrength(primary: any, secondary: any): string {
|
|
|
const primaryName = primary.name.toLowerCase();
|
|
|
const secondaryName = secondary?.name.toLowerCase() || 'blue';
|
|
|
|
|
|
const strengthMap: { [key: string]: string } = {
|
|
|
'blue-red': 'Their decisive nature pairs well with your thoughtful approach.',
|
|
|
'blue-yellow': 'Creative vision enhances your analytical thinking.',
|
|
|
'blue-green': 'Structured planning complements your methodical nature.',
|
|
|
'red-blue': 'Your decisive action is balanced by their thoughtful analysis.',
|
|
|
'red-yellow': 'Direct approach combines well with creative solutions.',
|
|
|
'red-green': 'Action-oriented style benefits from organized planning.',
|
|
|
'yellow-blue': 'Spontaneous ideas are grounded by logical thinking.',
|
|
|
'yellow-red': 'Creative energy is channeled through decisive action.',
|
|
|
'yellow-green': 'Visionary thinking is supported by practical organization.',
|
|
|
'green-blue': 'Organized approach enhances analytical capabilities.',
|
|
|
'green-red': 'Structured planning supports decisive implementation.',
|
|
|
'green-yellow': 'Methodical nature balances creative spontaneity.'
|
|
|
};
|
|
|
|
|
|
return strengthMap[`${primaryName}-${secondaryName}`] ||
|
|
|
'Their personality traits create complementary dynamics.';
|
|
|
}
|
|
|
|
|
|
|
|
|
private generateRisks(primary: any, secondary: any): any {
|
|
|
const primaryName = primary.name.toLowerCase();
|
|
|
const secondaryName = secondary?.name.toLowerCase() || 'blue';
|
|
|
|
|
|
const riskMap: { [key: string]: { communicationStyle: string; lifestylePace: string } } = {
|
|
|
'blue-red': {
|
|
|
communicationStyle: 'One may speak directly while the other needs time to process feelings',
|
|
|
lifestylePace: 'Methodical approach may conflict with fast-paced decision making'
|
|
|
},
|
|
|
'blue-yellow': {
|
|
|
communicationStyle: 'Structured communication may clash with free-flowing expression',
|
|
|
lifestylePace: 'Spontaneity vs routine may clash in day-to-day life'
|
|
|
},
|
|
|
'red-blue': {
|
|
|
communicationStyle: 'Direct communication style may overwhelm reflective processing',
|
|
|
lifestylePace: 'Fast-paced approach may conflict with deliberate decision making'
|
|
|
},
|
|
|
'red-yellow': {
|
|
|
communicationStyle: 'Action-oriented talk may miss creative nuances',
|
|
|
lifestylePace: 'Goal-driven pace may overlook imaginative possibilities'
|
|
|
},
|
|
|
'yellow-blue': {
|
|
|
communicationStyle: 'Expressive communication may lack structured detail',
|
|
|
lifestylePace: 'Flexible scheduling may frustrate planned routines'
|
|
|
},
|
|
|
'yellow-green': {
|
|
|
communicationStyle: 'Big-picture thinking may miss practical details',
|
|
|
lifestylePace: 'Spontaneous changes may disrupt organized systems'
|
|
|
},
|
|
|
'green-blue': {
|
|
|
communicationStyle: 'Process-focused discussion may lack emotional depth',
|
|
|
lifestylePace: 'Rigid routines may limit adaptive opportunities'
|
|
|
},
|
|
|
'green-red': {
|
|
|
communicationStyle: 'Detailed explanations may frustrate action-oriented listening',
|
|
|
lifestylePace: 'Structured planning may slow down decisive action'
|
|
|
}
|
|
|
};
|
|
|
|
|
|
return riskMap[`${primaryName}-${secondaryName}`] || {
|
|
|
communicationStyle: 'Different communication paces may require adjustment',
|
|
|
lifestylePace: 'Varying daily rhythms may need coordination'
|
|
|
};
|
|
|
}
|
|
|
|
|
|
|
|
|
private generateSacrifices(primary: any, secondary: any): any {
|
|
|
const primaryName = primary.name.toLowerCase();
|
|
|
const secondaryName = secondary?.name.toLowerCase() || 'blue';
|
|
|
|
|
|
const sacrificeMap: { [key: string]: { communication: string; emotionalNeeds: string; dailyRoutines: string } } = {
|
|
|
'blue-red': {
|
|
|
communication: 'The fast-responding partner must slow down during emotional conversations',
|
|
|
emotionalNeeds: 'The more reserved partner must communicate needs openly',
|
|
|
dailyRoutines: 'Both must compromise between structure and flexibility in daily routines'
|
|
|
},
|
|
|
'blue-yellow': {
|
|
|
communication: 'The structured thinker must allow space for creative expression',
|
|
|
emotionalNeeds: 'The spontaneous partner must provide advance notice for plans',
|
|
|
dailyRoutines: 'Both must balance routine with unexpected opportunities'
|
|
|
},
|
|
|
'red-blue': {
|
|
|
communication: 'The direct communicator must practice patience in discussions',
|
|
|
emotionalNeeds: 'The analytical partner must express feelings more immediately',
|
|
|
dailyRoutines: 'Action-oriented approach must accommodate thoughtful planning'
|
|
|
},
|
|
|
'red-yellow': {
|
|
|
communication: 'The goal-focused partner must listen to imaginative ideas',
|
|
|
emotionalNeeds: 'The creative partner must respect decision deadlines',
|
|
|
dailyRoutines: 'Both must blend practical action with innovative thinking'
|
|
|
},
|
|
|
'yellow-blue': {
|
|
|
communication: 'The big-picture thinker must provide specific details when needed',
|
|
|
emotionalNeeds: 'The methodical partner must embrace spontaneous emotional expression',
|
|
|
dailyRoutines: 'Flexible approach must adapt to important routines'
|
|
|
},
|
|
|
'yellow-green': {
|
|
|
communication: 'The visionary must ground ideas in practical reality',
|
|
|
emotionalNeeds: 'The organized partner must allow for unexpected emotional expressions',
|
|
|
dailyRoutines: 'Spontaneous nature must respect important schedules'
|
|
|
},
|
|
|
'green-blue': {
|
|
|
communication: 'The process-oriented partner must prioritize emotional connection',
|
|
|
emotionalNeeds: 'The analytical partner must express feelings beyond logical framework',
|
|
|
dailyRoutines: 'Structured approach must allow for adaptive changes'
|
|
|
},
|
|
|
'green-red': {
|
|
|
communication: 'The detailed planner must summarize key points for action-takers',
|
|
|
emotionalNeeds: 'The decisive partner must consider emotional impacts of quick decisions',
|
|
|
dailyRoutines: 'Organized systems must accommodate timely actions'
|
|
|
}
|
|
|
};
|
|
|
|
|
|
return sacrificeMap[`${primaryName}-${secondaryName}`] || {
|
|
|
communication: 'Both partners must adjust communication style to meet in the middle',
|
|
|
emotionalNeeds: 'Each must understand and respect different emotional expression styles',
|
|
|
dailyRoutines: 'Compromise needed between different lifestyle preferences'
|
|
|
};
|
|
|
}
|
|
|
|
|
|
private generateModeSpecificFallbackAnalysis(profile: Profile): void {
|
|
|
let analysis: CompatibilityAnalysis;
|
|
|
|
|
|
if (this.mode === 'character') {
|
|
|
analysis = this.generateCharacterFallbackAnalysis(profile);
|
|
|
} else if (this.mode === 'expectation') {
|
|
|
analysis = this.generateCombinedFallbackAnalysis(profile);
|
|
|
} else {
|
|
|
analysis = this.generateGenericFallbackAnalysis(profile);
|
|
|
}
|
|
|
|
|
|
this.compatibilityAnalysis = analysis;
|
|
|
}
|
|
|
|
|
|
|
|
|
private generateCharacterFallback(profile: Profile): string {
|
|
|
const characterScore = profile.character_score || 0;
|
|
|
|
|
|
|
|
|
const characterAnalysis = this.analyzeCharacterForSections(profile);
|
|
|
|
|
|
return `
|
|
|
<p>Based on personality color analysis, you have <strong>${characterScore}% character compatibility</strong> with ${profile.name}.</p>
|
|
|
<div class="character-sections">
|
|
|
<div class="section">
|
|
|
<strong>Character Strengths</strong>
|
|
|
<ul>
|
|
|
${characterAnalysis.strengths.map(s => `<li>${s}</li>`).join('')}
|
|
|
</ul>
|
|
|
</div>
|
|
|
<div class="section">
|
|
|
<strong>Character Risks</strong>
|
|
|
<ul>
|
|
|
${characterAnalysis.risks.map(r => `<li>${r}</li>`).join('')}
|
|
|
</ul>
|
|
|
</div>
|
|
|
<div class="section">
|
|
|
<strong>Sacrifices Needed</strong>
|
|
|
<ul>
|
|
|
${characterAnalysis.sacrifices.map(s => `<li>${s}</li>`).join('')}
|
|
|
</ul>
|
|
|
</div>
|
|
|
</div>
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
private generateCombinedFallbackAnalysis(profile: Profile): CompatibilityAnalysis {
|
|
|
const exactMatches: string[] = [];
|
|
|
const differences: FieldComparison[] = [];
|
|
|
const missingData: string[] = [];
|
|
|
|
|
|
const expectationScore = profile.expectation_score || 0;
|
|
|
const characterScore = profile.character_score || 0;
|
|
|
const overallScore = profile.match_score || 0;
|
|
|
|
|
|
exactMatches.push(`✅ Overall Compatibility: ${overallScore}% combined score`);
|
|
|
exactMatches.push(`✅ Expectation Alignment: ${expectationScore}% match with your criteria`);
|
|
|
exactMatches.push(`✅ Character Compatibility: ${characterScore}% personality match`);
|
|
|
|
|
|
if (expectationScore < characterScore) {
|
|
|
differences.push({
|
|
|
fieldName: 'Primary Strength',
|
|
|
yourExpectation: 'Expectation-focused alignment',
|
|
|
theirProfile: 'Character-focused compatibility',
|
|
|
isCritical: false
|
|
|
});
|
|
|
} else if (characterScore < expectationScore) {
|
|
|
differences.push({
|
|
|
fieldName: 'Primary Strength',
|
|
|
yourExpectation: 'Character-focused alignment',
|
|
|
theirProfile: 'Expectation-focused compatibility',
|
|
|
isCritical: false
|
|
|
});
|
|
|
}
|
|
|
|
|
|
missingData.push('⚠️ Detailed combined analysis breakdown');
|
|
|
missingData.push('⚠️ Weighted scoring details');
|
|
|
|
|
|
|
|
|
const totalComparisons = exactMatches.length + differences.length;
|
|
|
const compatibilityPercent = overallScore;
|
|
|
|
|
|
return {
|
|
|
exactMatches,
|
|
|
differences,
|
|
|
missingData,
|
|
|
compatibilityPercent,
|
|
|
totalExactMatches: exactMatches.length,
|
|
|
totalDifferences: differences.length,
|
|
|
totalMissingData: missingData.length
|
|
|
};
|
|
|
}
|
|
|
|
|
|
|
|
|
private generateExpectationFallbackAnalysis(profile: Profile): void {
|
|
|
const exactMatches: string[] = [];
|
|
|
const differences: FieldComparison[] = [];
|
|
|
const missingData: string[] = [];
|
|
|
|
|
|
|
|
|
const expectationScore = profile.expectation_score || 0;
|
|
|
|
|
|
exactMatches.push(`✅ Overall Expectation Match: ${expectationScore}% alignment`);
|
|
|
|
|
|
|
|
|
if (profile.city && this.userCity && profile.city === this.userCity) {
|
|
|
exactMatches.push(`✅ Location: Both in ${profile.city}`);
|
|
|
} else if (profile.city) {
|
|
|
differences.push({
|
|
|
fieldName: 'Location',
|
|
|
yourExpectation: this.userCity,
|
|
|
theirProfile: profile.city,
|
|
|
isCritical: true
|
|
|
});
|
|
|
}
|
|
|
|
|
|
if (profile.religion) {
|
|
|
exactMatches.push(`✅ Religion: ${profile.religion}`);
|
|
|
} else {
|
|
|
missingData.push(`⚠️ Religion: Not specified in profile`);
|
|
|
}
|
|
|
|
|
|
if (profile.education_level) {
|
|
|
exactMatches.push(`✅ Education: ${profile.education_level}`);
|
|
|
}
|
|
|
|
|
|
|
|
|
if (expectationScore < 80) {
|
|
|
differences.push({
|
|
|
fieldName: 'Income Expectations',
|
|
|
yourExpectation: 'As per your criteria',
|
|
|
theirProfile: 'Different range',
|
|
|
isCritical: false
|
|
|
});
|
|
|
}
|
|
|
|
|
|
const totalComparisons = exactMatches.length + differences.length;
|
|
|
const compatibilityPercent = expectationScore;
|
|
|
|
|
|
this.compatibilityAnalysis = {
|
|
|
exactMatches,
|
|
|
differences,
|
|
|
missingData,
|
|
|
compatibilityPercent,
|
|
|
totalExactMatches: exactMatches.length,
|
|
|
totalDifferences: differences.length,
|
|
|
totalMissingData: missingData.length
|
|
|
};
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private isActualMissingData(explanation: string): boolean {
|
|
|
|
|
|
if (explanation.toLowerCase().includes('country')) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (explanation.includes('field') || explanation.includes('data not available')) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
|
|
|
private generateGenericFallbackAnalysis(profile: Profile): CompatibilityAnalysis {
|
|
|
return {
|
|
|
exactMatches: [`✅ Basic compatibility: ${profile.match_score || 0}% match`],
|
|
|
differences: [],
|
|
|
missingData: ['⚠️ Detailed analysis not available for current mode'],
|
|
|
compatibilityPercent: profile.match_score || 0,
|
|
|
totalExactMatches: 1,
|
|
|
totalDifferences: 0,
|
|
|
totalMissingData: 0
|
|
|
};
|
|
|
}
|
|
|
|
|
|
|
|
|
private processCompatibilityAnalysis(explanations: string[]): void {
|
|
|
const exactMatches: string[] = [];
|
|
|
const differences: FieldComparison[] = [];
|
|
|
const missingData: string[] = [];
|
|
|
|
|
|
console.log('🔍 Raw explanations from backend:', explanations);
|
|
|
|
|
|
explanations.forEach(explanation => {
|
|
|
console.log('🔍 Processing explanation:', explanation);
|
|
|
|
|
|
|
|
|
if (explanation.includes('📊') ||
|
|
|
explanation.includes('Compatibility') ||
|
|
|
explanation.includes('matches out of') ||
|
|
|
explanation.includes('Missing Profile Data') ||
|
|
|
explanation.includes('Exact Matches') ||
|
|
|
explanation.includes('Differences')) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (explanation.includes('Profile missing')) {
|
|
|
missingData.push(explanation);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (explanation.includes('(Both match)')) {
|
|
|
|
|
|
const matchResult = explanation.match(/•\s*([^:]+):\s*([^(]+)\s*\(Both match\)/);
|
|
|
if (matchResult && matchResult.length >= 3) {
|
|
|
const fieldName = matchResult[1].trim();
|
|
|
const value = matchResult[2].trim();
|
|
|
|
|
|
exactMatches.push(`${fieldName}|${value}|${value}`);
|
|
|
console.log('✅ Found exact match:', { fieldName, value });
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (explanation.includes("You want '") && explanation.includes("', they have '")) {
|
|
|
|
|
|
const diffResult = explanation.match(/•\s*([^:]+):\s*You want\s*'([^']+)',\s*they have\s*'([^']+)'/);
|
|
|
if (diffResult && diffResult.length >= 4) {
|
|
|
const fieldName = diffResult[1].trim();
|
|
|
const yourExpectation = diffResult[2].trim();
|
|
|
const theirProfile = diffResult[3].trim();
|
|
|
|
|
|
differences.push({
|
|
|
fieldName: this.formatFieldName(fieldName),
|
|
|
yourExpectation: yourExpectation,
|
|
|
theirProfile: theirProfile,
|
|
|
isCritical: this.isCriticalField(fieldName, yourExpectation, theirProfile)
|
|
|
});
|
|
|
console.log('✅ Found difference:', { fieldName, yourExpectation, theirProfile });
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (explanation.includes('|') && !explanation.startsWith('•')) {
|
|
|
const parts = explanation.split('|');
|
|
|
if (parts.length === 3) {
|
|
|
const [fieldName, expectValue, profileValue] = parts.map(p => p.trim());
|
|
|
|
|
|
if (expectValue === profileValue) {
|
|
|
exactMatches.push(explanation);
|
|
|
console.log('✅ Found exact match (raw format):', { fieldName, expectValue });
|
|
|
} else {
|
|
|
differences.push({
|
|
|
fieldName: this.formatFieldName(fieldName),
|
|
|
yourExpectation: expectValue,
|
|
|
theirProfile: profileValue,
|
|
|
isCritical: this.isCriticalField(fieldName, expectValue, profileValue)
|
|
|
});
|
|
|
console.log('✅ Found difference (raw format):', { fieldName, expectValue, profileValue });
|
|
|
}
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (explanation.includes('Profile matches your preference for')) {
|
|
|
|
|
|
const fieldMatch = explanation.match(/for\s+([^(]+)\s+\(/);
|
|
|
const valueMatch = explanation.match(/\(([^)]+)\)/);
|
|
|
|
|
|
if (fieldMatch && valueMatch) {
|
|
|
const fieldName = fieldMatch[1].trim();
|
|
|
const value = valueMatch[1].trim();
|
|
|
exactMatches.push(`${fieldName}|${value}|${value}`);
|
|
|
}
|
|
|
} else if (explanation.includes('Profile differs from your preference for')) {
|
|
|
|
|
|
const fieldMatch = explanation.match(/for\s+([^(]+)\s+\(/);
|
|
|
const expectMatch = explanation.match(/you want:\s*([^,)]+)/);
|
|
|
const profileMatch = explanation.match(/they are:\s*([^)]+)/);
|
|
|
|
|
|
if (fieldMatch && expectMatch && profileMatch) {
|
|
|
const fieldName = fieldMatch[1].trim();
|
|
|
const yourExpectation = expectMatch[1].trim();
|
|
|
const theirProfile = profileMatch[1].trim();
|
|
|
|
|
|
differences.push({
|
|
|
fieldName: this.formatFieldName(fieldName),
|
|
|
yourExpectation: yourExpectation,
|
|
|
theirProfile: theirProfile,
|
|
|
isCritical: this.isCriticalField(fieldName, yourExpectation, theirProfile)
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
const totalComparisons = exactMatches.length + differences.length;
|
|
|
const compatibilityPercent = totalComparisons > 0 ?
|
|
|
Math.round((exactMatches.length / totalComparisons) * 100) : 0;
|
|
|
|
|
|
this.compatibilityAnalysis = {
|
|
|
exactMatches,
|
|
|
differences,
|
|
|
missingData,
|
|
|
compatibilityPercent,
|
|
|
totalExactMatches: exactMatches.length,
|
|
|
totalDifferences: differences.length,
|
|
|
totalMissingData: missingData.length
|
|
|
};
|
|
|
|
|
|
console.log('✅ Processed compatibility analysis:', {
|
|
|
exactMatches: this.compatibilityAnalysis.exactMatches,
|
|
|
differences: this.compatibilityAnalysis.differences,
|
|
|
totalExactMatches: this.compatibilityAnalysis.totalExactMatches,
|
|
|
totalDifferences: this.compatibilityAnalysis.totalDifferences
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
private isCriticalField(fieldName: string, yourExpectation: string, theirProfile: string): boolean {
|
|
|
const criticalFields = [
|
|
|
'location', 'religion', 'marital status', 'children',
|
|
|
'diet', 'smoking', 'alcohol', 'deal breakers'
|
|
|
];
|
|
|
|
|
|
const lowerFieldName = fieldName.toLowerCase();
|
|
|
|
|
|
|
|
|
if (criticalFields.some(critical => lowerFieldName.includes(critical))) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (lowerFieldName.includes('deal breaker')) {
|
|
|
return yourExpectation !== theirProfile;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
private parseDifferenceExplanation(explanation: string): FieldComparison | null {
|
|
|
|
|
|
|
|
|
const fieldMatch = explanation.match(/for\s+([^()]+)\s+\(/);
|
|
|
const expectationMatch = explanation.match(/you want:\s*([^,]+)/);
|
|
|
const profileMatch = explanation.match(/they are:\s*([^)]+)/);
|
|
|
|
|
|
if (fieldMatch && expectationMatch && profileMatch) {
|
|
|
const fieldName = fieldMatch[1].trim();
|
|
|
const yourExpectation = expectationMatch[1].trim();
|
|
|
const theirProfile = profileMatch[1].trim();
|
|
|
|
|
|
return {
|
|
|
fieldName: this.formatFieldName(fieldName),
|
|
|
yourExpectation: yourExpectation,
|
|
|
theirProfile: theirProfile,
|
|
|
isCritical: explanation.includes('🚫') || fieldName.toLowerCase().includes('location') || fieldName.toLowerCase().includes('religion')
|
|
|
};
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
|
|
|
private formatFieldName(fieldName: string): string {
|
|
|
const fieldMap: { [key: string]: string } = {
|
|
|
'location': 'Location',
|
|
|
'religion': 'Religion',
|
|
|
'age': 'Age Range',
|
|
|
'marital status': 'Marital Status',
|
|
|
'education level': 'Education Level',
|
|
|
'employment status': 'Employment Status',
|
|
|
'income range': 'Income Range',
|
|
|
'family type': 'Family Type',
|
|
|
'diet': 'Dietary Preference',
|
|
|
'smoking': 'Smoking Habits',
|
|
|
'alcohol': 'Alcohol Consumption',
|
|
|
'fitness level': 'Fitness Level',
|
|
|
'conflict style': 'Conflict Resolution',
|
|
|
'financial style': 'Financial Management',
|
|
|
'hobbies': 'Hobbies & Interests',
|
|
|
'travel': 'Travel Preference',
|
|
|
'pets': 'Pet Ownership',
|
|
|
'children': 'Children Timeline',
|
|
|
'adoption': 'Open to Adoption',
|
|
|
'relocation': 'Relocation Willingness',
|
|
|
'health': 'Health Considerations',
|
|
|
'skin tone': 'Skin Tone',
|
|
|
'daily routine': 'Daily Routine',
|
|
|
'family communication': 'Family Communication',
|
|
|
'relaxation': 'Relaxation Preferences',
|
|
|
'career': 'Career Aspirations',
|
|
|
'in-laws': 'Living with In-Laws',
|
|
|
'parents': 'Living with Parents',
|
|
|
'financial support': 'Financial Support to Parents'
|
|
|
};
|
|
|
|
|
|
return fieldMap[fieldName.toLowerCase()] || this.capitalizeFirstLetter(fieldName);
|
|
|
}
|
|
|
|
|
|
private capitalizeFirstLetter(text: string): string {
|
|
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
|
}
|
|
|
|
|
|
|
|
|
toggleExactMatches(): void {
|
|
|
this.showExactMatches = true;
|
|
|
this.showDifferences = false;
|
|
|
}
|
|
|
|
|
|
toggleDifferences(): void {
|
|
|
this.showExactMatches = false;
|
|
|
this.showDifferences = true;
|
|
|
}
|
|
|
|
|
|
|
|
|
getCompatibilityReport(profile: Profile): string {
|
|
|
if (!profile.explanations || profile.explanations.length === 0) {
|
|
|
return this.generateFallbackExplanation(profile);
|
|
|
}
|
|
|
|
|
|
return this.generateDetailedExplanation(profile);
|
|
|
}
|
|
|
|
|
|
private generateFallbackExplanation(profile: Profile): string {
|
|
|
const score = profile.match_score || 0;
|
|
|
|
|
|
let explanation = '';
|
|
|
|
|
|
switch (this.mode) {
|
|
|
case 'expectation-only':
|
|
|
explanation = this.generateExpectationFallback(profile);
|
|
|
break;
|
|
|
case 'character':
|
|
|
explanation = this.generateCharacterFallback(profile);
|
|
|
break;
|
|
|
case 'expectation':
|
|
|
explanation = this.generateCombinedFallback(profile);
|
|
|
break;
|
|
|
default:
|
|
|
explanation = this.generateGenericFallback(profile);
|
|
|
}
|
|
|
|
|
|
return `<div class="compatibility-report">
|
|
|
<p><strong>Compatibility Analysis (${this.getModeDisplayName()}):</strong></p>
|
|
|
${explanation}
|
|
|
<p><em>Score: ${score}% compatibility</em></p>
|
|
|
</div>`;
|
|
|
}
|
|
|
|
|
|
private generateDetailedExplanation(profile: Profile): string {
|
|
|
const score = profile.match_score || 0;
|
|
|
const sourceType = profile.explanation_source || 'unknown';
|
|
|
const explanations = profile.explanations || [];
|
|
|
|
|
|
return `<div class="compatibility-report">
|
|
|
<p><strong>${this.getModeDisplayName()} Analysis (${this.getSourceDisplayName(sourceType)}):</strong></p>
|
|
|
<ul>
|
|
|
${explanations.map(exp => `<li>${exp}</li>`).join('')}
|
|
|
</ul>
|
|
|
<p><em>Overall Score: ${score}%</em></p>
|
|
|
</div>`;
|
|
|
}
|
|
|
|
|
|
|
|
|
private generateExpectationFallback(profile: Profile): string {
|
|
|
const expectationScore = profile.expectation_score || 0;
|
|
|
return `
|
|
|
<p>Based on your relationship expectations and preferences, you have <strong>${expectationScore}% expectation alignment</strong> with ${profile.name}.</p>
|
|
|
<p><em>Detailed field-by-field analysis shows specific matches and differences.</em></p>
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private generateCombinedFallback(profile: Profile): string {
|
|
|
const expectationScore = profile.expectation_score || 0;
|
|
|
const characterScore = profile.character_score || 0;
|
|
|
return `
|
|
|
<p>This match combines both expectation alignment and character compatibility:</p>
|
|
|
<ul>
|
|
|
<li><strong>Expectation Match:</strong> ${expectationScore}% - Based on your relationship criteria and preferences</li>
|
|
|
<li><strong>Character Match:</strong> ${characterScore}% - Based on personality color compatibility</li>
|
|
|
<li><strong>Final Score:</strong> ${profile.match_score}% - Weighted combination of both factors</li>
|
|
|
</ul>
|
|
|
<p>The system prioritizes expectation alignment while considering character traits for refined matching.</p>
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
private generateGenericFallback(profile: Profile): string {
|
|
|
return `
|
|
|
<p>Based on your profile data, you have <strong>${profile.match_score}% compatibility</strong> with ${profile.name}.</p>
|
|
|
<p>This comprehensive match considers multiple factors including personality traits, expectations, and lifestyle preferences.</p>
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
|
|
|
public getModeDisplayName(): string {
|
|
|
switch (this.mode) {
|
|
|
case 'expectation-only': return 'Pure Expectation';
|
|
|
case 'character': return 'Pure Character';
|
|
|
case 'expectation': return 'Expectation + Character';
|
|
|
default: return 'Compatibility';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private getSourceDisplayName(source: string): string {
|
|
|
switch (source) {
|
|
|
case 'expectation': return 'Expectation Analysis';
|
|
|
case 'character': return 'Character Analysis';
|
|
|
case 'llm': return 'AI Analysis';
|
|
|
default: return 'System Analysis';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private analyzeColorTraits(profile: Profile): { dominantMatch: string, complementaryTraits: string, potentialDynamics: string } {
|
|
|
const colors = [
|
|
|
{ name: 'Blue', value: profile.blue || 0, trait: 'analytical and structured' },
|
|
|
{ name: 'Green', value: profile.green || 0, trait: 'organized and process-oriented' },
|
|
|
{ name: 'Yellow', value: profile.yellow || 0, trait: 'creative and visionary' },
|
|
|
{ name: 'Red', value: profile.red || 0, trait: 'decisive and action-driven' }
|
|
|
];
|
|
|
|
|
|
const dominantColor = colors.reduce((prev, current) =>
|
|
|
(prev.value > current.value) ? prev : current
|
|
|
);
|
|
|
|
|
|
let dynamics = '';
|
|
|
if (dominantColor.value > 40) {
|
|
|
dynamics = `Strong ${dominantColor.name} dominance suggests ${dominantColor.trait} approach to relationships`;
|
|
|
} else {
|
|
|
dynamics = 'Balanced color profile indicates adaptable and well-rounded personality';
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
dominantMatch: `Dominant ${dominantColor.name} traits (${dominantColor.value}%)`,
|
|
|
complementaryTraits: `Personality shows ${dominantColor.trait} characteristics`,
|
|
|
potentialDynamics: dynamics
|
|
|
};
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getFieldNameFromMatch(match: string): string {
|
|
|
if (!match) return 'Unknown Field';
|
|
|
|
|
|
try {
|
|
|
console.log('🔍 Parsing field name from:', match);
|
|
|
|
|
|
|
|
|
|
|
|
if (match.includes('|')) {
|
|
|
const parts = match.split('|');
|
|
|
if (parts.length >= 1) {
|
|
|
const fieldName = parts[0].trim();
|
|
|
console.log('✅ Extracted field name from pipe format:', fieldName);
|
|
|
return this.formatFieldName(fieldName);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
if (match.includes('Profile matches your preference for')) {
|
|
|
const fieldMatch = match.match(/for\s+([^(]+)\s+\(/);
|
|
|
if (fieldMatch && fieldMatch[1]) {
|
|
|
const fieldName = fieldMatch[1].trim();
|
|
|
console.log('✅ Extracted field name from "Profile matches" format:', fieldName);
|
|
|
return this.formatFieldName(fieldName);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
if (match.includes(':') && match.includes('(Both match)')) {
|
|
|
const colonIndex = match.indexOf(':');
|
|
|
if (colonIndex > -1) {
|
|
|
|
|
|
let fieldName = match.substring(0, colonIndex).trim();
|
|
|
fieldName = fieldName.replace('•', '').trim();
|
|
|
console.log('✅ Extracted field name from "Both match" format:', fieldName);
|
|
|
return this.formatFieldName(fieldName);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
const colonIndex = match.indexOf(':');
|
|
|
if (colonIndex > -1) {
|
|
|
let fieldName = match.substring(0, colonIndex).trim();
|
|
|
|
|
|
fieldName = fieldName.replace(/[✅❌🎯👤•]/g, '').trim();
|
|
|
console.log('✅ Extracted field name from colon format:', fieldName);
|
|
|
return this.formatFieldName(fieldName);
|
|
|
}
|
|
|
|
|
|
|
|
|
console.log('⚠️ Could not parse field name, formatting raw:', match);
|
|
|
return this.formatFieldName(match);
|
|
|
} catch (error) {
|
|
|
console.error('Error parsing field name from match:', error);
|
|
|
return 'Field';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getExpectationValueFromMatch(match: string): string {
|
|
|
if (!match) return 'Not specified';
|
|
|
|
|
|
try {
|
|
|
console.log('🔍 Parsing expectation value from:', match);
|
|
|
|
|
|
|
|
|
|
|
|
if (match.includes('|')) {
|
|
|
const parts = match.split('|');
|
|
|
if (parts.length >= 2) {
|
|
|
const value = parts[1].trim();
|
|
|
console.log('✅ Extracted value from pipe format:', value);
|
|
|
return value;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
const parenthesesMatch = match.match(/\(([^)]+)\)/);
|
|
|
if (parenthesesMatch && parenthesesMatch[1]) {
|
|
|
const value = parenthesesMatch[1].trim();
|
|
|
console.log('✅ Extracted value from parentheses:', value);
|
|
|
return value;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (match.includes('you want:')) {
|
|
|
const expectationMatch = match.match(/you want:\s*([^,)]+)/);
|
|
|
if (expectationMatch && expectationMatch[1]) {
|
|
|
const value = expectationMatch[1].trim();
|
|
|
console.log('✅ Extracted value from "you want:" format:', value);
|
|
|
return value;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
const colonIndex = match.indexOf(':');
|
|
|
if (colonIndex > -1) {
|
|
|
const value = match.substring(colonIndex + 1).trim();
|
|
|
console.log('✅ Extracted value from colon format:', value);
|
|
|
return value;
|
|
|
}
|
|
|
|
|
|
console.log('⚠️ Could not parse value, returning raw:', match);
|
|
|
return match;
|
|
|
} catch (error) {
|
|
|
console.error('Error parsing expectation value from match:', error);
|
|
|
return 'Match found';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
getProfileValueFromMatch(match: string): string {
|
|
|
if (!match) return 'Not specified';
|
|
|
|
|
|
try {
|
|
|
console.log('🔍 Parsing profile value from:', match);
|
|
|
|
|
|
|
|
|
if (match.includes('|')) {
|
|
|
const parts = match.split('|');
|
|
|
if (parts.length >= 3) {
|
|
|
const value = parts[2].trim();
|
|
|
console.log('✅ Extracted profile value from pipe format:', value);
|
|
|
return value;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
if (match.includes('they are:')) {
|
|
|
const profileMatch = match.match(/they are:\s*([^)]+)/);
|
|
|
if (profileMatch && profileMatch[1]) {
|
|
|
const value = profileMatch[1].trim();
|
|
|
console.log('✅ Extracted profile value from "they are:" format:', value);
|
|
|
return value;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
const expectationValue = this.getExpectationValueFromMatch(match);
|
|
|
console.log('✅ Using expectation value as profile value:', expectationValue);
|
|
|
return expectationValue;
|
|
|
} catch (error) {
|
|
|
console.error('Error parsing profile value from match:', error);
|
|
|
return 'Match found';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private getConsistentPhoto(userId: number, gender?: string): string {
|
|
|
if (this.userPhotoMap.has(userId)) {
|
|
|
return this.userPhotoMap.get(userId)!;
|
|
|
}
|
|
|
|
|
|
const profileGender = (gender || 'male').toLowerCase().trim();
|
|
|
const isFemale = profileGender === 'female' ||
|
|
|
profileGender === 'f' ||
|
|
|
profileGender.includes('female') ||
|
|
|
profileGender.includes('woman') ||
|
|
|
profileGender.includes('girl');
|
|
|
|
|
|
const photoPool = isFemale ? this.indianFemalePhotos : this.indianMalePhotos;
|
|
|
const poolSize = photoPool.length;
|
|
|
|
|
|
let selectedIndex = -1;
|
|
|
let attempts = 0;
|
|
|
const maxAttempts = poolSize * 2;
|
|
|
|
|
|
while (attempts < maxAttempts) {
|
|
|
let candidateIndex = Math.floor(Math.random() * poolSize);
|
|
|
|
|
|
if (this.usedPhotoIndices.size === 0) {
|
|
|
selectedIndex = candidateIndex;
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
const isSuitable = this.isPhotoSuitable(candidateIndex, isFemale, poolSize);
|
|
|
|
|
|
if (isSuitable) {
|
|
|
selectedIndex = candidateIndex;
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
attempts++;
|
|
|
|
|
|
if (attempts >= maxAttempts / 2) {
|
|
|
for (let i = 0; i < poolSize; i++) {
|
|
|
if (this.isPhotoSuitable(i, isFemale, poolSize)) {
|
|
|
selectedIndex = i;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (selectedIndex !== -1) break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (selectedIndex === -1) {
|
|
|
selectedIndex = this.getLeastRecentlyUsedIndex(isFemale, poolSize);
|
|
|
}
|
|
|
|
|
|
this.usedPhotoIndices.add(selectedIndex);
|
|
|
this.updateRecentIndices(selectedIndex, isFemale);
|
|
|
|
|
|
if (isFemale) {
|
|
|
this.lastUsedFemaleIndex = selectedIndex;
|
|
|
} else {
|
|
|
this.lastUsedMaleIndex = selectedIndex;
|
|
|
}
|
|
|
|
|
|
const selectedPhoto = photoPool[selectedIndex];
|
|
|
this.userPhotoMap.set(userId, selectedPhoto);
|
|
|
|
|
|
return selectedPhoto;
|
|
|
}
|
|
|
|
|
|
private isPhotoSuitable(candidateIndex: number, isFemale: boolean, poolSize: number): boolean {
|
|
|
const recentIndices = isFemale ? this.recentFemaleIndices : this.recentMaleIndices;
|
|
|
const lastUsedIndex = isFemale ? this.lastUsedFemaleIndex : this.lastUsedMaleIndex;
|
|
|
|
|
|
if (recentIndices.includes(candidateIndex)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if (candidateIndex === lastUsedIndex) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if (this.usedPhotoIndices.size >= poolSize * 0.7) {
|
|
|
return !recentIndices.slice(-3).includes(candidateIndex);
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
private updateRecentIndices(index: number, isFemale: boolean): void {
|
|
|
const recentIndices = isFemale ? this.recentFemaleIndices : this.recentMaleIndices;
|
|
|
|
|
|
recentIndices.push(index);
|
|
|
|
|
|
if (recentIndices.length > this.MAX_RECENT_INDICES) {
|
|
|
recentIndices.shift();
|
|
|
}
|
|
|
|
|
|
if (isFemale) {
|
|
|
this.recentFemaleIndices = recentIndices;
|
|
|
} else {
|
|
|
this.recentMaleIndices = recentIndices;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private getLeastRecentlyUsedIndex(isFemale: boolean, poolSize: number): number {
|
|
|
const recentIndices = isFemale ? this.recentFemaleIndices : this.recentMaleIndices;
|
|
|
|
|
|
for (let i = 0; i < poolSize; i++) {
|
|
|
if (!recentIndices.includes(i)) {
|
|
|
return i;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return recentIndices[0] || 0;
|
|
|
}
|
|
|
|
|
|
public resetPhotoTracking(): void {
|
|
|
this.usedPhotoIndices.clear();
|
|
|
this.recentMaleIndices = [];
|
|
|
this.recentFemaleIndices = [];
|
|
|
this.lastUsedMaleIndex = -1;
|
|
|
this.lastUsedFemaleIndex = -1;
|
|
|
}
|
|
|
|
|
|
|
|
|
onSearchChange(): void {
|
|
|
console.log('Search term:', this.searchTerm);
|
|
|
}
|
|
|
|
|
|
|
|
|
applyFilters(): void {
|
|
|
|
|
|
this.processMatchesForDisplay();
|
|
|
|
|
|
|
|
|
if (this.selectedRange && this.selectedRange !== 'all') {
|
|
|
this.displayedMatches = this.displayedMatches.filter(profile => {
|
|
|
const score = profile.match_score || 0;
|
|
|
|
|
|
switch (this.selectedRange) {
|
|
|
case '90-100':
|
|
|
return score >= 90 && score <= 100;
|
|
|
case '80-89':
|
|
|
return score >= 80 && score < 90;
|
|
|
case '70-79':
|
|
|
return score >= 70 && score < 80;
|
|
|
case '60-69':
|
|
|
return score >= 60 && score < 70;
|
|
|
case 'below_60':
|
|
|
return score < 60;
|
|
|
default:
|
|
|
return true;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
if (this.selectedLocation) {
|
|
|
this.displayedMatches = this.displayedMatches.filter(profile =>
|
|
|
profile.city === this.selectedLocation
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (this.selectedProfession) {
|
|
|
this.displayedMatches = this.displayedMatches.filter(profile =>
|
|
|
profile.job_role === this.selectedProfession
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (this.selectedEducation) {
|
|
|
this.displayedMatches = this.displayedMatches.filter(profile =>
|
|
|
profile.education_level === this.selectedEducation
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (this.selectedReligion) {
|
|
|
this.displayedMatches = this.displayedMatches.filter(profile =>
|
|
|
profile.religion === this.selectedReligion
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (this.selectedAgeRange) {
|
|
|
this.displayedMatches = this.displayedMatches.filter(profile => {
|
|
|
if (!profile.age) return false;
|
|
|
const [minAge, maxAge] = this.selectedAgeRange.split('-').map(Number);
|
|
|
return profile.age >= minAge && profile.age <= maxAge;
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
clearFilters(): void {
|
|
|
this.selectedLocation = '';
|
|
|
this.selectedProfession = '';
|
|
|
this.selectedEducation = '';
|
|
|
this.selectedReligion = '';
|
|
|
this.selectedAgeRange = '';
|
|
|
this.selectedRange = 'all';
|
|
|
|
|
|
|
|
|
this.processMatchesForDisplay();
|
|
|
}
|
|
|
|
|
|
|
|
|
closeModal(): void {
|
|
|
this.showProfileModal = false;
|
|
|
this.selectedProfile = null;
|
|
|
this.showUserInfo = false;
|
|
|
this.compatibilityAnalysis = null;
|
|
|
this.showExactMatches = false;
|
|
|
this.showDifferences = true;
|
|
|
}
|
|
|
|
|
|
openEditModal(): void {
|
|
|
this.loadUserProfileData(this.userId!);
|
|
|
this.showUserProfileModal = true;
|
|
|
}
|
|
|
|
|
|
|
|
|
editProfile(): void {
|
|
|
if (this.userId) {
|
|
|
this.router.navigate(['/question-answer'], {
|
|
|
queryParams: {
|
|
|
role: 'marriage',
|
|
|
user_id: this.userId
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
closeUserProfileModal(): void {
|
|
|
this.showUserProfileModal = false;
|
|
|
}
|
|
|
|
|
|
|
|
|
closeEditModal(): void {
|
|
|
this.showEditModal = false;
|
|
|
}
|
|
|
|
|
|
saveProfile(): void {
|
|
|
this.userName = this.editName;
|
|
|
this.userCity = this.editCity;
|
|
|
this.closeEditModal();
|
|
|
}
|
|
|
|
|
|
|
|
|
toggleLike(profile: Profile): void {
|
|
|
const index = this.likedProfiles.findIndex(p => p.user_id === profile.user_id);
|
|
|
if (index > -1) {
|
|
|
this.likedProfiles.splice(index, 1);
|
|
|
} else {
|
|
|
this.likedProfiles.push(profile);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
isLiked(profile: Profile): boolean {
|
|
|
return this.likedProfiles.some(p => p.user_id === profile.user_id);
|
|
|
}
|
|
|
|
|
|
toggleLikedDropdown(event: Event): void {
|
|
|
event.stopPropagation();
|
|
|
this.showLikedDropdown = !this.showLikedDropdown;
|
|
|
}
|
|
|
|
|
|
|
|
|
toggleUserInfo(): void {
|
|
|
this.showUserInfo = !this.showUserInfo;
|
|
|
if (this.showUserInfo && this.userId && !this.userProfileData) {
|
|
|
this.loadUserProfileData(this.userId);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
loadUserProfileData(userId: number): void {
|
|
|
this.loading = true;
|
|
|
this.matchingService.getMarriageProfile(userId).subscribe({
|
|
|
next: (profile: any) => {
|
|
|
this.userProfileData = profile;
|
|
|
|
|
|
if (profile) {
|
|
|
this.userName = profile.full_name || this.userName;
|
|
|
this.userCity = profile.current_city || this.userCity;
|
|
|
}
|
|
|
this.loading = false;
|
|
|
},
|
|
|
error: (err: any) => {
|
|
|
console.error('Failed to load user profile data:', err);
|
|
|
this.loading = false;
|
|
|
|
|
|
this.userProfileData = {
|
|
|
full_name: this.userName,
|
|
|
current_city: this.userCity
|
|
|
};
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
openChat(profile: Profile): void {
|
|
|
this.currentChatUser = profile;
|
|
|
this.chatMessages = [
|
|
|
{ sender: 'them', text: 'Hello! How are you?', time: '10:00 AM' },
|
|
|
{ sender: 'me', text: 'I\'m good, thanks! How about you?', time: '10:01 AM' }
|
|
|
];
|
|
|
this.showChatWindow = true;
|
|
|
}
|
|
|
|
|
|
closeChat(): void {
|
|
|
this.showChatWindow = false;
|
|
|
this.currentChatUser = null;
|
|
|
this.chatMessages = [];
|
|
|
}
|
|
|
|
|
|
sendMessage(): void {
|
|
|
if (this.newMessage.trim()) {
|
|
|
const now = new Date();
|
|
|
const timeString = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
|
|
|
|
this.chatMessages.push({
|
|
|
sender: 'me',
|
|
|
text: this.newMessage,
|
|
|
time: timeString
|
|
|
});
|
|
|
|
|
|
this.newMessage = '';
|
|
|
|
|
|
setTimeout(() => {
|
|
|
this.showTyping = true;
|
|
|
setTimeout(() => {
|
|
|
this.showTyping = false;
|
|
|
this.chatMessages.push({
|
|
|
sender: 'them',
|
|
|
text: 'That\'s interesting! Tell me more.',
|
|
|
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
|
});
|
|
|
}, 2000);
|
|
|
}, 1000);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
typingIndicator(): void {
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
handleImageError(event: any, profile: Profile): void {
|
|
|
event.target.src = 'https://cdn-icons-png.flaticon.com/512/3135/3135715.png';
|
|
|
}
|
|
|
|
|
|
get filteredGroups() {
|
|
|
if (this.selectedRange === 'all') {
|
|
|
const order = ['90-100', '80-89', '70-79', '60-69', 'below_60'];
|
|
|
return this.groupedMatches
|
|
|
.filter(g => g.items.length > 0)
|
|
|
.sort((a, b) => order.indexOf(a.range) - order.indexOf(b.range));
|
|
|
}
|
|
|
|
|
|
return this.groupedMatches.filter((g) => g.range === this.selectedRange && g.items.length > 0);
|
|
|
}
|
|
|
|
|
|
clearPhotoCache(): void {
|
|
|
this.userPhotoMap.clear();
|
|
|
this.usedPhotos.clear();
|
|
|
this.resetPhotoTracking();
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|