py-match / src /app /matching-list /matching-list.component.ts
pykara's picture
dropdown enabled
8885c59
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;
// Marriage profile fields
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;
// Additional fields
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;
// User data
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[] = [];
// User profile data
userProfileData: any = null;
userExpectationData: any = null;
showUserInfo: boolean = false;
// Loading state
loading = false;
error: string | null = null;
// Matching data
result: ApiMatchResponse | null = null;
groupedMatches: { range: string; items: ApiMatchItem[] }[] = [];
displayedMatches: Profile[] = [];
// Filters and search
searchTerm: string = '';
// Dynamic filter options
availableLocations: string[] = [];
availableAgeRanges: string[] = ['18-25', '26-35', '36-45', '46+'];
availableProfessions: string[] = [];
availableEducationLevels: string[] = [];
availableReligions: string[] = [];
// Selected filters
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'];
// Default to 'character' so the "All Matches" button is active by default
mode: 'expectation-only' | 'expectation' | 'character' = 'character';
// Modals and UI states
showProfileModal: boolean = false;
showEditModal: boolean = false;
showChatWindow: boolean = false;
showLikedDropdown: boolean = false;
// Selected profile data
selectedProfile: Profile | null = null;
likedProfiles: Profile[] = [];
// Chat functionality
currentChatUser: Profile | null = null;
chatMessages: ChatMessage[] = [];
newMessage: string = '';
showTyping: boolean = false;
// Edit profile
editName: string = '';
editCity: string = '';
// Compatibility Analysis
compatibilityAnalysis: CompatibilityAnalysis | null = null;
showExactMatches: boolean = false;
showDifferences: boolean = true;
setActiveTab(tab: 'details' | 'compatibility'): void {
this.activeTab = tab;
// When switching to compatibility tab, ensure data is loaded
if (tab === 'compatibility' && this.selectedProfile) {
console.log('🔄 Switching to compatibility tab, checking data...');
// If in character mode and no character data loaded yet, fetch it
if (this.mode === 'character' && this.strengths.length === 0 && this.risks.length === 0 && this.sacrifices.length === 0) {
console.log('🎯 Loading character data on tab switch...');
/* this.fetchCompatibilityExplanation(this.selectedProfile);*/
}
// Log current state for debugging
console.log('📊 Current character data state:', {
strengths: this.strengths,
risks: this.risks,
sacrifices: this.sacrifices,
profileExplanations: this.selectedProfile.explanations
});
}
}
// Store marriage profiles data
private marriageProfiles: Map<number, any> = new Map();
private userPhotoMap: Map<number, string> = new Map();
private usedPhotos: Set<string> = new Set();
// Photo distribution tracking
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
// Use the component's current mode (default set above) when fetching matches
.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');
// Reapply filters after mode change
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');
// Reapply filters after mode change
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');
// Reapply filters after mode change
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);
}
// Collect filter options
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,
// Map database fields
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);
});
});
// Update filter options with collected data
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;
}
// 🚨 UPDATED: Enhanced compatibility explanation methods
/* fetchCompatibilityExplanation(profile: Profile): void {
if (!this.userId) return;
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) {
*//* updatedProfile.explanations = response.explanations;
updatedProfile.explanation_source = this.mode === 'expectation-only' ? 'expectation' :
this.mode === 'character' ? 'character' : 'combined';*//*
updatedProfile.explanations = response.explanations;
updatedProfile.explanation_source = response.source;
}
},
error: (err) => {
console.error('Failed to fetch compatibility explanation:', err);
}
});
}*/
fetchCompatibilityExplanation(profile: Profile): void {
if (!this.userId) return;
// ✅ PREVENT DUPLICATE CALLS
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; // Skip adding section header to content
}
else if (lowerLine.includes('risk') && currentSection !== 'risks') {
currentSection = 'risks';
console.log('🎯 Frontend: Switching to risks section');
return; // Skip adding section header to content
}
else if (lowerLine.includes('sacrific') && currentSection !== 'sacrifices') {
currentSection = 'sacrifices';
console.log('🎯 Frontend: Switching to sacrifices section');
return; // Skip adding section header to content
}
// Numbered section detection with duplicate prevention
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;
}
}
// Content lines (not section headers) - only add if we have a current section
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);
}
}
});
// Fallback: if we didn't find sections but have content, distribute it
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);
}
// Ensure each section has at least some content
this.ensureMinimumContent();
// Split long content into multiple points if needed
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() {
// Split very long content into multiple bullet points for better readability
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;
// Simple distribution: first third to strengths, second to risks, last to sacrifices
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() {
// If any section is empty but others have content, redistribute
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()!);
}
}
// Ensure we have at least one item in each section if possible
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()!);
}
}
}
}
// Call this when opening profile modal
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;
// Reset subTab based on mode
if (this.mode === 'expectation') {
this.subTab = 'expectation'; // Default to expectation tab for combined mode
} else {
this.subTab = 'expectation'; // Default for other modes
}
console.log('🟢 Opening profile modal for:', profile.name, 'Mode:', this.mode);
// Reset character sections
this.strengths = [];
this.risks = [];
this.sacrifices = [];
// Load analysis based on mode
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...');
// Load both for combined mode
this.loadCompatibilityAnalysis(profile); // This loads expectation analysis
this.fetchCompatibilityExplanation(profile); // This loads character analysis
}
}
private loadCompatibilityAnalysis(profile: Profile): void {
if (!this.userId) return;
console.log('🟢 loadCompatibilityAnalysis called for mode:', this.mode);
// For Character mode, use the character explanation
if (this.mode === 'character') {
console.log('🎯 Character mode - fetching character explanation');
this.fetchCompatibilityExplanation(profile);
}
// For Expectation and Combined modes, fetch detailed analysis
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' }
];
// Find dominant and secondary colors
const sortedColors = [...colors].sort((a, b) => b.value - a.value);
const dominant = sortedColors[0];
const secondary = sortedColors[1];
// Generate dynamic content based on color combinations
const strengths = this.generateCharacterStrengths(dominant, secondary);
const risks = this.generateCharacterRisks(dominant, secondary);
const sacrifices = this.generateCharacterSacrifices(dominant, secondary);
return { strengths, risks, sacrifices };
}
// 🚨 UPDATED: Character mode fallback analysis with LLM-style structure
private generateCharacterFallbackAnalysis(profile: Profile): CompatibilityAnalysis {
const exactMatches: string[] = [];
const differences: FieldComparison[] = [];
const missingData: string[] = [];
// Get character score and color analysis using existing function
const characterScore = profile.character_score || 0;
const characterAnalysis = this.analyzeCharacterForSections(profile);
// Character Strengths (Exact Matches)
characterAnalysis.strengths.slice(0, 3).forEach(strength => {
exactMatches.push(`✅ ${strength}`);
});
// Character Risks (Differences)
characterAnalysis.risks.slice(0, 2).forEach(risk => {
differences.push({
fieldName: 'Personality Dynamics',
yourExpectation: 'Harmonious interaction',
theirProfile: risk,
isCritical: false
});
});
// Sacrifices Needed (Missing Data / Areas for Growth)
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
};
}
// 🚨 NEW: Detailed color analysis generator matching LLM format
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'] }
];
// Sort by dominance
const dominantColors = [...colors].sort((a, b) => b.value - a.value);
const primary = dominantColors[0];
const secondary = dominantColors[1];
// Generate strengths based on color combinations
const strengths = this.generateStrengths(primary, secondary);
const risks = this.generateRisks(primary, secondary);
const sacrifices = this.generateSacrifices(primary, secondary);
return { strengths, risks, sacrifices };
}
// Add this method to handle range filter changes
onRangeChange(): void {
this.applyFilters();
}
// 🚨 NEW: Generate character strengths in LLM format
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."
];
// Select strengths based on color combinations
const selectedStrengths = [
strengthTemplates[0], // Always include the first one
this.getSpecificStrength(primary, secondary),
strengthTemplates[5] // Always include emotional patterns
];
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.';
}
// 🚨 NEW: Generate character risks in LLM format
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'
};
}
// 🚨 NEW: Generate sacrifices needed in LLM format
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'
};
}
// 🚨 NEW: Generate fallback analysis for Character and Combined modes
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;
}
// 🚨 NEW: Character mode fallback analysis
private generateCharacterFallback(profile: Profile): string {
const characterScore = profile.character_score || 0;
// Get structured character analysis using existing function
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>
`;
}
// 🚨 NEW: Combined mode fallback analysis
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
};
}
// 🚨 UPDATED: Expectation mode fallback (when backend fails)
private generateExpectationFallbackAnalysis(profile: Profile): void {
const exactMatches: string[] = [];
const differences: FieldComparison[] = [];
const missingData: string[] = [];
// Simulate expectation-based analysis
const expectationScore = profile.expectation_score || 0;
exactMatches.push(`✅ Overall Expectation Match: ${expectationScore}% alignment`);
// Simulate some field comparisons based on available profile data
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}`);
}
// Add some simulated differences for demonstration
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
};
}
//// Add this public getter to your component class
//get modeDisplayName(): string {
// switch (this.mode) {
// case 'expectation-only': return 'Pure Expectation';
// case 'character': return 'Pure Character';
// case 'expectation': return 'Expectation + Character';
// default: return 'Compatibility';
// }
//}
private isActualMissingData(explanation: string): boolean {
// Country "missing" messages are often just informational, not actual missing data
if (explanation.toLowerCase().includes('country')) {
return false;
}
// Real missing data would be fields that are completely empty/null
if (explanation.includes('field') || explanation.includes('data not available')) {
return true;
}
return false;
}
// 🚨 NEW: Generic fallback for unexpected modes
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
};
}
// 🚨 UPDATED: Process backend explanations with proper parsing for new format
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);
// Skip summary and header lines
if (explanation.includes('📊') ||
explanation.includes('Compatibility') ||
explanation.includes('matches out of') ||
explanation.includes('Missing Profile Data') ||
explanation.includes('Exact Matches') ||
explanation.includes('Differences')) {
return;
}
// Parse missing data
if (explanation.includes('Profile missing')) {
missingData.push(explanation);
return;
}
// Parse exact matches from new format: "• Location: New York (Both match)"
if (explanation.includes('(Both match)')) {
// Extract field name and value from "• Location: New York (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();
// Store in pipe-separated format for frontend parsing
exactMatches.push(`${fieldName}|${value}|${value}`);
console.log('✅ Found exact match:', { fieldName, value });
}
return;
}
// Parse differences from new format: "• Height: You want '180-190 cm', they have '175 cm'"
if (explanation.includes("You want '") && explanation.includes("', they have '")) {
// Extract from "• Height: You want '180-190 cm', they have '175 cm'"
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;
}
// Try to parse raw pipe-separated format directly (if backend sends it raw)
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;
}
// Handle old format for backward compatibility
if (explanation.includes('Profile matches your preference for')) {
// Old format exact match
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')) {
// Old format difference
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
});
}
// Add this helper method to determine critical fields
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();
// These are always critical if they don't match
if (criticalFields.some(critical => lowerFieldName.includes(critical))) {
return true;
}
// For deal breakers specifically
if (lowerFieldName.includes('deal breaker')) {
return yourExpectation !== theirProfile;
}
return false;
}
// 🚨 NEW: Parse difference explanation from backend format
private parseDifferenceExplanation(explanation: string): FieldComparison | null {
// Example backend format: "Profile differs from your preference for {field} (you want: {expectation}, they are: {profile})"
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;
}
// 🚨 NEW: Format field names for better display
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;
}
// 🚨 UPDATED: Enhanced compatibility report generation
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>`;
}
// Mode-specific fallback explanations
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 generateCharacterFallback(profile: Profile): string {
const characterScore = profile.character_score || 0;
const colorAnalysis = this.analyzeColorTraits(profile);
return `
<p>Based on personality color analysis, you have <strong>${characterScore}% character compatibility</strong> with ${profile.name}.</p>
<ul>
<li>${colorAnalysis.dominantMatch}</li>
<li>${colorAnalysis.complementaryTraits}</li>
<li>${colorAnalysis.potentialDynamics}</li>
</ul>
`;
}*/
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>
`;
}
// Helper methods
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
};
}
// MISSING METHODS - Add these to fix the template errors
/**
* Extract field name from match string
*/
/**
* Extract field name from match string
*/
getFieldNameFromMatch(match: string): string {
if (!match) return 'Unknown Field';
try {
console.log('🔍 Parsing field name from:', match);
// NEW BACKEND FORMAT: "field|expect_value|profile_value"
// Example: "Fitness|Moderate|Moderate" → should return "Fitness"
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);
}
}
// OLD BACKEND FORMAT: "• Profile matches your preference for location (New York)"
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);
}
}
// FORMAT: "• Location: New York (Both match)"
if (match.includes(':') && match.includes('(Both match)')) {
const colonIndex = match.indexOf(':');
if (colonIndex > -1) {
// Remove the bullet point if present
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);
}
}
// Simple format: "✅ Location: Both in New York"
const colonIndex = match.indexOf(':');
if (colonIndex > -1) {
let fieldName = match.substring(0, colonIndex).trim();
// Remove emojis and symbols
fieldName = fieldName.replace(/[✅❌🎯👤•]/g, '').trim();
console.log('✅ Extracted field name from colon format:', fieldName);
return this.formatFieldName(fieldName);
}
// Fallback: return the match as-is (should be field name)
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';
}
}
/**
* Extract expectation value from match string
*/
getExpectationValueFromMatch(match: string): string {
if (!match) return 'Not specified';
try {
console.log('🔍 Parsing expectation value from:', match);
// NEW BACKEND FORMAT: "field|expect_value|profile_value"
// Example: "Diet|Vegetarian|Vegetarian" or "Location|New York|New York"
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;
}
}
// OLD BACKEND FORMAT: "• Profile matches your preference for location (New York)"
const parenthesesMatch = match.match(/\(([^)]+)\)/);
if (parenthesesMatch && parenthesesMatch[1]) {
const value = parenthesesMatch[1].trim();
console.log('✅ Extracted value from parentheses:', value);
return value;
}
// OLD BACKEND FORMAT: "Profile matches your preference for location (you want: New York, they are: New York)"
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;
}
}
// SIMPLE FORMAT: "✅ Location: Both in New York"
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);
// NEW BACKEND FORMAT: "field|expect_value|profile_value"
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;
}
}
// OLD BACKEND FORMAT: "Profile matches your preference for location (you want: New York, they are: New York)"
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;
}
}
// For exact matches, the value is the same as expectation
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';
}
}
// Photo distribution methods
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;
}
// Filter methods
onSearchChange(): void {
console.log('Search term:', this.searchTerm);
}
// Update the applyFilters method to handle all modes properly
applyFilters(): void {
// First, reprocess matches for the current mode
this.processMatchesForDisplay();
// Then apply the range filter
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;
}
});
}
// Apply other filters
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;
});
}
}
// Update the mode switching methods to preserve and reapply filters
/*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');
// Reapply filters after mode change
setTimeout(() => this.applyFilters(), 0);
},
error: (err: any) => {
this.error = err?.error?.error || 'Failed to fetch expectation matches.';
this.loading = false;
},
complete: () => (this.loading = false),
});
}
*/
clearFilters(): void {
this.selectedLocation = '';
this.selectedProfession = '';
this.selectedEducation = '';
this.selectedReligion = '';
this.selectedAgeRange = '';
this.selectedRange = 'all';
// Reprocess matches and reset to show all
this.processMatchesForDisplay();
}
// Profile methods
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();
}
// Like functionality
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;
}
// User info methods
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;
// Update user details if available
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;
// Show fallback data
this.userProfileData = {
full_name: this.userName,
current_city: this.userCity
};
}
});
}
// Chat functionality
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 {
// Typing indicator logic
}
// Utility methods
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();
}
}