Swetha commited on
Commit
139688b
·
1 Parent(s): 4c34168

matching-list component upadted

Browse files
package-lock.json CHANGED
@@ -10,9 +10,9 @@
10
  "dependencies": {
11
  "@angular/animations": "^16.2.12",
12
  "@angular/cdk": "^16.2.14",
13
- "@angular/common": "^16.1.0",
14
  "@angular/compiler": "^16.1.0",
15
- "@angular/core": "^16.1.0",
16
  "@angular/forms": "^16.1.0",
17
  "@angular/material": "^16.2.14",
18
  "@angular/platform-browser": "^16.1.0",
@@ -21,7 +21,6 @@
21
  "@fortawesome/fontawesome-free": "^7.0.0",
22
  "jest-editor-support": "*",
23
  "rxjs": "~7.8.0",
24
- "tslib": "^2.3.0",
25
  "zone.js": "~0.13.0"
26
  },
27
  "devDependencies": {
@@ -35,6 +34,7 @@
35
  "karma-coverage": "~2.2.0",
36
  "karma-jasmine": "~5.1.0",
37
  "karma-jasmine-html-reporter": "~2.1.0",
 
38
  "typescript": "~5.1.3"
39
  }
40
  },
 
10
  "dependencies": {
11
  "@angular/animations": "^16.2.12",
12
  "@angular/cdk": "^16.2.14",
13
+ "@angular/common": "^16.2.12",
14
  "@angular/compiler": "^16.1.0",
15
+ "@angular/core": "^16.2.12",
16
  "@angular/forms": "^16.1.0",
17
  "@angular/material": "^16.2.14",
18
  "@angular/platform-browser": "^16.1.0",
 
21
  "@fortawesome/fontawesome-free": "^7.0.0",
22
  "jest-editor-support": "*",
23
  "rxjs": "~7.8.0",
 
24
  "zone.js": "~0.13.0"
25
  },
26
  "devDependencies": {
 
34
  "karma-coverage": "~2.2.0",
35
  "karma-jasmine": "~5.1.0",
36
  "karma-jasmine-html-reporter": "~2.1.0",
37
+ "tslib": "^2.8.1",
38
  "typescript": "~5.1.3"
39
  }
40
  },
package.json CHANGED
@@ -12,9 +12,9 @@
12
  "dependencies": {
13
  "@angular/animations": "^16.2.12",
14
  "@angular/cdk": "^16.2.14",
15
- "@angular/common": "^16.1.0",
16
  "@angular/compiler": "^16.1.0",
17
- "@angular/core": "^16.1.0",
18
  "@angular/forms": "^16.1.0",
19
  "@angular/material": "^16.2.14",
20
  "@angular/platform-browser": "^16.1.0",
@@ -23,7 +23,6 @@
23
  "@fortawesome/fontawesome-free": "^7.0.0",
24
  "jest-editor-support": "*",
25
  "rxjs": "~7.8.0",
26
- "tslib": "^2.3.0",
27
  "zone.js": "~0.13.0"
28
  },
29
  "devDependencies": {
@@ -37,6 +36,7 @@
37
  "karma-coverage": "~2.2.0",
38
  "karma-jasmine": "~5.1.0",
39
  "karma-jasmine-html-reporter": "~2.1.0",
 
40
  "typescript": "~5.1.3"
41
  }
42
  }
 
12
  "dependencies": {
13
  "@angular/animations": "^16.2.12",
14
  "@angular/cdk": "^16.2.14",
15
+ "@angular/common": "^16.2.12",
16
  "@angular/compiler": "^16.1.0",
17
+ "@angular/core": "^16.2.12",
18
  "@angular/forms": "^16.1.0",
19
  "@angular/material": "^16.2.14",
20
  "@angular/platform-browser": "^16.1.0",
 
23
  "@fortawesome/fontawesome-free": "^7.0.0",
24
  "jest-editor-support": "*",
25
  "rxjs": "~7.8.0",
 
26
  "zone.js": "~0.13.0"
27
  },
28
  "devDependencies": {
 
36
  "karma-coverage": "~2.2.0",
37
  "karma-jasmine": "~5.1.0",
38
  "karma-jasmine-html-reporter": "~2.1.0",
39
+ "tslib": "^2.8.1",
40
  "typescript": "~5.1.3"
41
  }
42
  }
src/app/matching-list/matching-list.component.css CHANGED
@@ -353,11 +353,12 @@
353
  background: #fff;
354
  padding: 25px;
355
  border-radius: 10px;
356
- width: 450px;
357
- max-height: 90vh;
358
- overflow-y: auto;
359
  text-align: center;
360
  position: relative;
 
 
361
  }
362
 
363
  .ai-summary {
@@ -584,3 +585,130 @@
584
  bottom: 10px;
585
  }
586
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
  background: #fff;
354
  padding: 25px;
355
  border-radius: 10px;
356
+ width: 38vw;
357
+ height: 73vh;
 
358
  text-align: center;
359
  position: relative;
360
+ top: 12%;
361
+ overflow-y: scroll;
362
  }
363
 
364
  .ai-summary {
 
585
  bottom: 10px;
586
  }
587
  }
588
+
589
+
590
+ /* Mode Toggle */
591
+ .mode-toggle {
592
+ display: flex;
593
+ margin-left: 10px;
594
+ }
595
+
596
+ .mode-btn {
597
+ padding: 8px 12px;
598
+ border: 1px solid #00bfa6;
599
+ background: white;
600
+ color: #00bfa6;
601
+ cursor: pointer;
602
+ transition: all 0.3s ease;
603
+ }
604
+
605
+ .mode-btn:first-child {
606
+ border-radius: 6px 0 0 6px;
607
+ }
608
+
609
+ .mode-btn:last-child {
610
+ border-radius: 0 6px 6px 0;
611
+ }
612
+
613
+ .mode-btn.active {
614
+ background: #00bfa6;
615
+ color: white;
616
+ }
617
+
618
+ /* Range Filter */
619
+ .range-filter {
620
+ display: flex;
621
+ justify-content: space-between;
622
+ align-items: center;
623
+ margin-bottom: 20px;
624
+ padding: 10px;
625
+ background: white;
626
+ border-radius: 8px;
627
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
628
+ }
629
+
630
+ .range-filter select {
631
+ padding: 8px 12px;
632
+ border-radius: 6px;
633
+ border: 1px solid #ccc;
634
+ }
635
+
636
+ .mode-indicator {
637
+ font-size: 14px;
638
+ color: #666;
639
+ font-weight: 500;
640
+ }
641
+
642
+ /* Modal Profile Image */
643
+ .modal-profile-img {
644
+ width: 150px;
645
+ height: 150px;
646
+ border-radius: 50%;
647
+ object-fit: cover;
648
+ margin-bottom: 15px;
649
+ }
650
+
651
+ /* Personality Scores */
652
+ .personality-scores {
653
+ margin: 15px 0;
654
+ padding: 15px;
655
+ background: #f8f9fa;
656
+ border-radius: 8px;
657
+ }
658
+
659
+ .personality-scores h4 {
660
+ margin: 0 0 10px 0;
661
+ color: #333;
662
+ }
663
+
664
+ .trait-grid {
665
+ display: grid;
666
+ grid-template-columns: 1fr 1fr;
667
+ gap: 10px;
668
+ }
669
+
670
+ .trait {
671
+ padding: 8px 12px;
672
+ border-radius: 6px;
673
+ color: white;
674
+ font-weight: bold;
675
+ text-align: center;
676
+ }
677
+
678
+ .trait.blue {
679
+ background: #3498db;
680
+ }
681
+
682
+ .trait.green {
683
+ background: #2ecc71;
684
+ }
685
+
686
+ .trait.yellow {
687
+ background: #f39c12;
688
+ }
689
+
690
+ .trait.red {
691
+ background: #e74c3c;
692
+ }
693
+
694
+ /* Error State */
695
+ .error-message {
696
+ background: #fee;
697
+ color: #c33;
698
+ padding: 15px;
699
+ border-radius: 8px;
700
+ margin: 20px;
701
+ text-align: center;
702
+ border: 1px solid #fcc;
703
+ }
704
+ .profile-detail {
705
+ margin: 8px 0;
706
+ padding: 8px;
707
+ background: #f8f9fa;
708
+ border-radius: 6px;
709
+ font-size: 14px;
710
+ }
711
+
712
+ .profile-detail strong {
713
+ color: #333;
714
+ }
src/app/matching-list/matching-list.component.html CHANGED
@@ -10,8 +10,21 @@
10
  <!-- HEADER -->
11
  <div class="header-bar">
12
  <div class="left-section">
13
- <img src="https://cdn-icons-png.flaticon.com/512/168/168724.png" class="logo" alt="logo">
14
- <h2>Py-Match</h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  </div>
16
  <div class="search-section">
17
  <input type="text" id="searchBox" placeholder="Search name or city"
@@ -39,6 +52,20 @@
39
  <option value="location">Closest Location</option>
40
  </select>
41
  <button class="filter-btn" (click)="applyFilters()">Apply</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  </div>
43
  <div class="like-section" (click)="toggleLikedDropdown()">
44
  ❤️
@@ -72,6 +99,16 @@
72
 
73
  <!-- RIGHT MATCHING CONTAINER -->
74
  <div class="matching-container">
 
 
 
 
 
 
 
 
 
 
75
  <div class="grid-view" id="profilesContainer">
76
  <!-- Skeleton Loaders -->
77
  <div class="skeleton" *ngIf="displayedMatches.length === 0 && !loading"></div>
@@ -103,7 +140,7 @@
103
  <button (click)="$event.stopPropagation(); openChat(profile)">
104
  💬
105
  </button>
106
- <button (click)="$event.stopPropagation(); openModal(profile)">
107
  👁️
108
  </button>
109
  </div>
@@ -113,20 +150,42 @@
113
  </div>
114
  </div>
115
 
 
116
  <!-- PROFILE MODAL -->
117
  <div class="modal" [class.show]="showProfileModal">
118
  <div class="modal-content" *ngIf="selectedProfile">
119
  <span class="close" (click)="closeModal()">&times;</span>
120
  <img [src]="selectedProfile.photo_url" [alt]="selectedProfile.name"
121
- (error)="handleImageError($event, selectedProfile)">
122
- <h3>{{ selectedProfile.name }} ({{ selectedProfile.gender || 'Male' }})</h3>
123
  <p>{{ selectedProfile.age }} • {{ selectedProfile.city }}</p>
124
- <p>{{ selectedProfile.job }}</p>
125
- <p>Overall Match: {{ selectedProfile.match_score || selectedProfile.score }}%</p>
126
 
127
- <div class="ai-summary" [innerHTML]="getCompatibilityReport(selectedProfile)"></div>
 
 
 
 
 
 
 
 
 
128
 
129
- <canvas id="aiChart" style="display: none;"></canvas>
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  </div>
131
  </div>
132
 
 
10
  <!-- HEADER -->
11
  <div class="header-bar">
12
  <div class="left-section">
13
+ <!--<img src="https://cdn-icons-png.flaticon.com/512/168/168724.png" class="logo" alt="logo">
14
+ <h2>Py-Match</h2>-->
15
+ <!-- Mode Toggle -->
16
+ <div class="mode-toggle">
17
+ <button class="mode-btn"
18
+ [class.active]="mode === 'expectation'"
19
+ (click)="sortByExpectation()">
20
+ Expectation
21
+ </button>
22
+ <button class="mode-btn"
23
+ [class.active]="mode === 'character'"
24
+ (click)="sortCharacterwise()">
25
+ Character
26
+ </button>
27
+ </div>
28
  </div>
29
  <div class="search-section">
30
  <input type="text" id="searchBox" placeholder="Search name or city"
 
52
  <option value="location">Closest Location</option>
53
  </select>
54
  <button class="filter-btn" (click)="applyFilters()">Apply</button>
55
+
56
+ <!-- Mode Toggle -->
57
+ <!--<div class="mode-toggle">
58
+ <button class="mode-btn"
59
+ [class.active]="mode === 'expectation'"
60
+ (click)="sortByExpectation()">
61
+ Expectation
62
+ </button>
63
+ <button class="mode-btn"
64
+ [class.active]="mode === 'character'"
65
+ (click)="sortCharacterwise()">
66
+ Character
67
+ </button>
68
+ </div>-->
69
  </div>
70
  <div class="like-section" (click)="toggleLikedDropdown()">
71
  ❤️
 
99
 
100
  <!-- RIGHT MATCHING CONTAINER -->
101
  <div class="matching-container">
102
+ <!-- Score Range Filter -->
103
+ <div class="range-filter">
104
+ <select [(ngModel)]="selectedRange" (change)="processMatchesForDisplay()">
105
+ <option *ngFor="let range of availableRanges" [value]="range">
106
+ {{ range === 'all' ? 'All Scores' : range === 'below_60' ? 'Below 60%' : range + '%' }}
107
+ </option>
108
+ </select>
109
+ <span class="mode-indicator">Mode: {{ mode === 'expectation' ? 'Expectation Matching' : 'Character Matching' }}</span>
110
+ </div>
111
+
112
  <div class="grid-view" id="profilesContainer">
113
  <!-- Skeleton Loaders -->
114
  <div class="skeleton" *ngIf="displayedMatches.length === 0 && !loading"></div>
 
140
  <button (click)="$event.stopPropagation(); openChat(profile)">
141
  💬
142
  </button>
143
+ <button (click)="$event.stopPropagation(); openProfileModal(profile)">
144
  👁️
145
  </button>
146
  </div>
 
150
  </div>
151
  </div>
152
 
153
+ <!-- PROFILE MODAL -->
154
  <!-- PROFILE MODAL -->
155
  <div class="modal" [class.show]="showProfileModal">
156
  <div class="modal-content" *ngIf="selectedProfile">
157
  <span class="close" (click)="closeModal()">&times;</span>
158
  <img [src]="selectedProfile.photo_url" [alt]="selectedProfile.name"
159
+ (error)="handleImageError($event, selectedProfile)" class="modal-profile-img">
160
+ <h3>{{ selectedProfile.name }} ({{ selectedProfile.gender || 'Not specified' }})</h3>
161
  <p>{{ selectedProfile.age }} • {{ selectedProfile.city }}</p>
162
+ <!--<p>{{ selectedProfile.job }}</p>-->
 
163
 
164
+ <!-- Additional profile information -->
165
+ <div *ngIf="selectedProfile.marital_status" class="profile-detail">
166
+ <strong>Marital Status:</strong> {{ selectedProfile.marital_status }}
167
+ </div>
168
+ <div *ngIf="selectedProfile.education_level" class="profile-detail">
169
+ <strong>Education:</strong> {{ selectedProfile.education_level }}
170
+ </div>
171
+ <div *ngIf="selectedProfile.employment_status" class="profile-detail">
172
+ <strong>Employment:</strong> {{ selectedProfile.employment_status }}
173
+ </div>
174
 
175
+ <p><strong>Overall Match: {{ selectedProfile.match_score || selectedProfile.score }}%</strong></p>
176
+
177
+ <!-- Personality Scores -->
178
+ <!--<div class="personality-scores" *ngIf="selectedProfile.blue !== undefined">
179
+ <h4>Personality Traits:</h4>-->
180
+ <!--<div class="trait-grid">
181
+ <div class="trait blue">Blue: {{ selectedProfile.blue }}%</div>
182
+ <div class="trait green">Green: {{ selectedProfile.green }}%</div>
183
+ <div class="trait yellow">Yellow: {{ selectedProfile.yellow }}%</div>
184
+ <div class="trait red">Red: {{ selectedProfile.red }}%</div>
185
+ </div>-->
186
+ <!--</div>-->
187
+
188
+ <div class="ai-summary" [innerHTML]="getCompatibilityReport(selectedProfile)"></div>
189
  </div>
190
  </div>
191
 
src/app/matching-list/matching-list.component.ts CHANGED
@@ -1,304 +1,415 @@
1
- import { Component, OnInit, ElementRef, ViewChild, HostListener } from '@angular/core';
2
- import { Router } from '@angular/router';
3
- import { FormsModule } from '@angular/forms';
4
- import { MatchingService, ApiMatchItem, ApiMatchResponse } from '../matching.service';
5
  import { CommonModule } from '@angular/common';
 
 
 
6
 
7
- interface Profile extends ApiMatchItem {
8
- id?: number;
9
- name?: string;
10
- age?: number | null;
11
- city?: string | null;
12
- job?: string | null;
 
13
  isOnline?: boolean;
14
  match_score?: number;
15
- photo_url?: string;
16
- blue: number;
17
- green: number;
18
- yellow: number;
19
- red: number;
20
- score: number;
21
- user_id: number;
22
  gender?: string;
23
- profession?: string;
24
- meetsExpectations?: boolean;
25
- expectationMatchScore?: number;
26
- aiExplanation?: string;
27
- hobbies?: string[];
28
- education?: string;
29
- location?: string;
 
 
 
 
 
 
30
  }
31
 
32
  interface ChatMessage {
33
- text: string;
34
  sender: 'me' | 'them';
 
35
  time: string;
36
  }
37
 
38
  @Component({
39
  selector: 'app-matching-list',
40
- templateUrl: './matching-list.component.html',
41
- styleUrls: ['./matching-list.component.css'],
42
  standalone: true,
43
- imports: [FormsModule, CommonModule]
 
 
44
  })
45
  export class MatchingListComponent implements OnInit {
46
- @ViewChild('chatBody') chatBody!: ElementRef;
47
-
 
 
 
 
 
 
 
48
  loading = false;
49
  error: string | null = null;
50
 
51
- // Modal states
52
- showProfileModal = false;
53
- showEditModal = false;
54
- showChatWindow = false;
55
- showLikedDropdown = false;
56
- showTyping = false;
57
 
58
- // User data
59
- userId: number = 1;
60
- userPhoto = 'https://randomuser.me/api/portraits/men/15.jpg';
61
- userName = 'Ravi Kumar';
62
- userCity = 'Chennai, India';
63
- userProfileCompletion = 80;
64
 
65
- // Edit profile
66
- editName = '';
67
- editCity = '';
68
-
69
- // Filter state
70
- searchTerm = '';
71
- locationFilter = '';
72
- ageFilter = '';
73
- professionFilter = '';
74
- scoreFilter = 0;
75
- sortBy = 'compatibility';
76
-
77
- // Data
78
- allMatches: Profile[] = [];
79
- filteredMatches: Profile[] = [];
80
  selectedProfile: Profile | null = null;
81
- currentChatUser: Profile | null = null;
82
 
83
- // Chat state
 
84
  chatMessages: ChatMessage[] = [];
85
- newMessage = '';
86
- typingTimeout: any = null;
87
 
88
- // Stats
89
- totalMatches = 0;
90
-
91
- // Available filters
92
- availableLocations: string[] = ['Chennai', 'Bengaluru', 'Hyderabad', 'Mumbai', 'Delhi'];
93
- availableAgeRanges: string[] = ['18-25', '26-30', '31-35', '36-40', '41+'];
94
- availableProfessions: string[] = ['Software Engineer', 'Doctor', 'Teacher', 'Business', 'Student', 'Architect', 'Data Scientist', 'Marketing Analyst'];
95
 
96
- // Like functionality
97
- likedProfiles: Profile[] = [];
98
 
99
  constructor(
100
- private router: Router,
101
- private matchingService: MatchingService
102
  ) { }
103
 
104
- async ngOnInit() {
105
- this.loadMatches();
 
 
 
 
 
 
 
 
 
106
  }
107
 
108
- get displayedMatches(): Profile[] {
109
- return this.filteredMatches;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  }
111
 
112
- async loadMatches() {
113
- this.loading = true;
114
  this.error = null;
 
 
 
 
115
 
116
- try {
117
- this.matchingService.getMatches(this.userId, {
118
- role: 'marriage',
119
- limit: 50,
120
- excludeSelf: true
121
- }).subscribe({
122
- next: (response: ApiMatchResponse) => {
123
- this.allMatches = response.matches.map((match: ApiMatchItem) => ({
124
- ...match,
125
- id: match.user_id,
126
- user_id: match.user_id,
127
- name: match.name || `User ${match.user_id}`,
128
- match_score: match.score,
129
- age: match.age || Math.floor(Math.random() * 15) + 25,
130
- city: match.city || this.availableLocations[Math.floor(Math.random() * this.availableLocations.length)],
131
- job: match.role || this.availableProfessions[Math.floor(Math.random() * this.availableProfessions.length)],
132
- profession: match.role || this.availableProfessions[Math.floor(Math.random() * this.availableProfessions.length)],
133
- isOnline: Math.random() > 0.5,
134
- photo_url: match.photo_url || `https://randomuser.me/api/portraits/${Math.random() > 0.5 ? 'men' : 'women'}/${Math.floor(Math.random() * 50)}.jpg`,
135
- gender: Math.random() > 0.5 ? 'Male' : 'Female',
136
- meetsExpectations: false,
137
- expectationMatchScore: 0,
138
- aiExplanation: this.generateAIExplanation(match),
139
- hobbies: ['Reading', 'Travel', 'Music'].slice(0, Math.floor(Math.random() * 3) + 1),
140
- education: ['Bachelor', 'Master', 'PhD'][Math.floor(Math.random() * 3)],
141
- location: match.city || 'Unknown'
142
- }));
143
-
144
- this.applyFilters();
145
- this.totalMatches = this.allMatches.length;
146
  this.loading = false;
147
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  error: (err: any) => {
149
- console.error('Error loading matches:', err);
150
- this.error = 'Failed to load matches. Please try again.';
151
  this.loading = false;
152
- }
 
153
  });
 
154
 
155
- } catch (err) {
156
- this.error = 'Failed to load matches. Please try again.';
157
- this.loading = false;
158
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  }
160
 
161
- generateAIExplanation(profile: ApiMatchItem): string {
162
- const traits = [];
163
- if (profile.blue > 60) traits.push('analytical thinking');
164
- if (profile.green > 60) traits.push('empathetic nature');
165
- if (profile.yellow > 60) traits.push('creative approach');
166
- if (profile.red > 60) traits.push('assertive communication');
167
 
168
- if (traits.length > 0) {
169
- return `Strong compatibility in ${traits.join(' and ')} based on personality assessment.`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  }
 
171
 
172
- return 'Good overall compatibility with balanced personality traits.';
 
 
 
 
 
 
 
173
  }
174
 
175
- applyFilters() {
176
- let filtered = [...this.allMatches];
177
 
178
- // Apply search filter
179
- if (this.searchTerm) {
180
- const term = this.searchTerm.toLowerCase();
181
- filtered = filtered.filter(profile =>
182
- profile.name?.toLowerCase().includes(term) ||
183
- profile.city?.toLowerCase().includes(term) ||
184
- profile.job?.toLowerCase().includes(term) ||
185
- profile.hobbies?.some(hobby => hobby.toLowerCase().includes(term))
186
- );
187
- }
188
 
189
- // Apply location filter
190
- if (this.locationFilter) {
191
- filtered = filtered.filter(profile =>
192
- profile.city?.toLowerCase().includes(this.locationFilter.toLowerCase())
193
- );
194
- }
195
 
196
- // Apply age filter
197
- if (this.ageFilter) {
198
- filtered = filtered.filter(profile => {
199
- if (!profile.age) return false;
200
- return this.doesAgeMatch(profile.age, this.ageFilter);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  });
202
- }
203
-
204
- // Apply profession filter
205
- if (this.professionFilter) {
206
- filtered = filtered.filter(profile =>
207
- profile.profession?.toLowerCase().includes(this.professionFilter.toLowerCase())
208
- );
209
- }
210
 
211
- // Apply score filter
212
- if (this.scoreFilter > 0) {
213
- filtered = filtered.filter(profile =>
214
- (profile.match_score || profile.score) >= this.scoreFilter
215
- );
216
- }
 
217
 
218
- // Apply sorting
219
- filtered.sort((a, b) => {
220
- switch (this.sortBy) {
221
- case 'compatibility':
222
- return (b.match_score || b.score) - (a.match_score || a.score);
223
- case 'newest':
224
- return Math.random() - 0.5; // Random for demo
225
- case 'location':
226
- return a.city?.localeCompare(b.city || '') || 0;
227
- default:
228
- return 0;
229
  }
230
- });
231
 
232
- this.filteredMatches = filtered;
 
 
 
 
 
 
 
 
233
  }
234
 
235
- doesAgeMatch(age: number, ageRange: string): boolean {
236
- const ranges: { [key: string]: [number, number] } = {
237
- '18-25': [18, 25],
238
- '26-30': [26, 30],
239
- '31-35': [31, 35],
240
- '36-40': [36, 40],
241
- '41+': [41, 100]
242
- };
 
 
 
243
 
244
- const range = ranges[ageRange];
245
- return range ? age >= range[0] && age <= range[1] : false;
246
  }
247
 
248
- // Filter handlers
249
- onSearchChange() {
250
- this.applyFilters();
 
251
  }
252
 
253
- onLocationFilterChange(event: any) {
254
- this.locationFilter = event.target.value;
255
- this.applyFilters();
256
  }
257
 
258
- onAgeFilterChange(event: any) {
259
- this.ageFilter = event.target.value;
260
- this.applyFilters();
261
  }
262
 
263
- onProfessionFilterChange(event: any) {
264
- this.professionFilter = event.target.value;
265
- this.applyFilters();
266
  }
267
 
268
- onScoreFilterChange(event: any) {
269
- this.scoreFilter = parseInt(event.target.value);
270
- this.applyFilters();
271
  }
272
 
273
- onSortChange(event: any) {
274
- this.sortBy = event.target.value;
275
- this.applyFilters();
276
  }
277
 
278
- // Profile actions
279
- openModal(profile: Profile) {
 
 
 
 
 
280
  this.selectedProfile = profile;
281
  this.showProfileModal = true;
282
  }
283
 
284
- closeModal() {
285
  this.showProfileModal = false;
286
  this.selectedProfile = null;
287
  }
288
 
289
- openProfileModal(profile: Profile) {
290
- this.selectedProfile = profile;
291
- this.showProfileModal = true;
 
292
  }
293
 
294
- toggleLike(profile: Profile) {
295
- const index = this.likedProfiles.findIndex(p => p.user_id === profile.user_id);
 
296
 
 
 
 
 
 
 
 
 
 
 
 
297
  if (index > -1) {
298
  this.likedProfiles.splice(index, 1);
299
  } else {
300
  this.likedProfiles.push(profile);
301
- this.triggerConfetti();
302
  }
303
  }
304
 
@@ -306,176 +417,92 @@ export class MatchingListComponent implements OnInit {
306
  return this.likedProfiles.some(p => p.user_id === profile.user_id);
307
  }
308
 
309
- triggerConfetti() {
310
- // Simple confetti effect - you can enhance this with a proper confetti library
311
- console.log('Confetti effect triggered!');
312
- }
313
-
314
- toggleLikedDropdown() {
315
  this.showLikedDropdown = !this.showLikedDropdown;
316
  }
317
 
318
- // Edit profile
319
- openEditModal() {
320
- this.editName = this.userName;
321
- this.editCity = this.userCity.split(', ')[0]; // Remove country for edit
322
- this.showEditModal = true;
323
- }
324
-
325
- closeEditModal() {
326
- this.showEditModal = false;
327
- }
328
-
329
- saveProfile() {
330
- this.userName = this.editName;
331
- this.userCity = this.editCity + ', India';
332
- this.userProfileCompletion = 100;
333
- this.showEditModal = false;
334
- }
335
-
336
  // Chat functionality
337
- openChat(profile: Profile) {
338
  this.currentChatUser = profile;
 
 
 
 
339
  this.showChatWindow = true;
340
- this.loadChat(profile);
341
- setTimeout(() => {
342
- this.scrollChatToBottom();
343
- }, 100);
344
  }
345
 
346
- closeChat() {
347
  this.showChatWindow = false;
348
  this.currentChatUser = null;
349
  this.chatMessages = [];
350
  }
351
 
352
- typingIndicator() {
353
- this.showTyping = true;
354
- if (this.typingTimeout) {
355
- clearTimeout(this.typingTimeout);
356
- }
357
- this.typingTimeout = setTimeout(() => {
358
- this.showTyping = false;
359
- }, 1500);
360
- }
361
-
362
- sendMessage() {
363
- const text = this.newMessage.trim();
364
- if (!text || !this.currentChatUser) return;
365
-
366
- const message: ChatMessage = {
367
- text,
368
- sender: 'me',
369
- time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
370
- };
371
-
372
- this.chatMessages.push(message);
373
- this.saveMessage(this.currentChatUser, message);
374
- this.newMessage = '';
375
- this.scrollChatToBottom();
376
-
377
- // Simulate reply after 1 second
378
- setTimeout(() => {
379
- const reply: ChatMessage = {
380
- text: `${this.currentChatUser?.name}: Nice to hear from you!`,
381
- sender: 'them',
382
- time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
383
- };
384
- this.chatMessages.push(reply);
385
- this.saveMessage(this.currentChatUser!, reply);
386
- this.scrollChatToBottom();
387
- }, 1000);
388
- }
389
-
390
- saveMessage(user: Profile, message: ChatMessage) {
391
- const key = `chat_${user.user_id}`;
392
- let arr = JSON.parse(localStorage.getItem(key) || '[]');
393
- arr.push(message);
394
- localStorage.setItem(key, JSON.stringify(arr));
395
- }
396
-
397
- loadChat(user: Profile) {
398
- const key = `chat_${user.user_id}`;
399
- const arr = JSON.parse(localStorage.getItem(key) || '[]');
400
- this.chatMessages = arr;
401
- }
402
-
403
- scrollChatToBottom() {
404
- if (this.chatBody) {
405
- this.chatBody.nativeElement.scrollTop = this.chatBody.nativeElement.scrollHeight;
406
  }
407
  }
408
 
409
- // Compatibility report
410
- getCompatibilityReport(profile: Profile): string {
411
- const colorSet = {
412
- B: Math.floor(Math.random() * 40),
413
- G: Math.floor(Math.random() * 40),
414
- Y: Math.floor(Math.random() * 40),
415
- R: Math.floor(Math.random() * 40),
416
- };
417
- const colorMatch = (Math.random() * (0.9 - 0.6) + 0.6).toFixed(2);
418
- const expectationMatch = (Math.random() * (0.9 - 0.6) + 0.6).toFixed(2);
419
-
420
- const differences = [
421
- "Their religion or faith expectations differ (same faith vs no preference).",
422
- "Both share similar preferences in smoking preference (Occasionally).",
423
- "Their alcohol preference expectations differ (no preference vs never).",
424
- "Their dietary habits expectations differ (vegetarian vs no preference).",
425
- "Both share similar preferences in family type (Either).",
426
- "Their fitness lifestyle expectations differ (high vs moderate).",
427
- "Their preferred location expectations differ (pune vs kolkata).",
428
- "Their preferred country expectations differ (other vs australia).",
429
- ];
430
-
431
- return `
432
- <div class="compatibility-report">
433
- <p><strong>${profile.name}</strong> (${profile.gender || 'Male'})</p>
434
- <p><strong>Overall Match:</strong> ${profile.match_score || profile.score}%</p>
435
- <p><strong>Colors:</strong> B${colorSet.B} • G${colorSet.G} • Y${colorSet.Y} • R${colorSet.R}</p>
436
- <p><strong>Color Match:</strong> ${colorMatch} • <strong>Expectation Match:</strong> ${expectationMatch}</p>
437
- <br>
438
- <div class="quadratic-section">
439
- <h4>1️⃣ How this profile matches to you</h4>
440
- <p>${profile.name} shows high compatibility in emotional stability, career orientation, and communication approach. You both value similar ethics, decision-making styles, and emotional awareness.</p>
441
-
442
- <h4>2️⃣ Why the remaining percentage does not match</h4>
443
- <p>The mismatch comes mainly from lifestyle and belief areas. Factors like faith flexibility, preferred diet, and regional lifestyle contribute to the gap.</p>
444
-
445
- <h4>3️⃣ What you may need to sacrifice</h4>
446
- <p>For smoother compatibility, consider being more adaptive toward dietary and cultural choices, and show patience toward different viewpoints in lifestyle preferences.</p>
447
-
448
- <h4>4️⃣ Expectation-wise Comparison</h4>
449
- <ul>
450
- ${differences
451
- .map(
452
- (d) =>
453
- `<li style="color:${d.includes("similar") ? "green" : "red"
454
- }">${d}</li>`
455
- )
456
- .join("")}
457
- </ul>
458
- </div>
459
- </div>`;
460
  }
461
 
462
- // Utility functions
463
- handleImageError(event: any, profile: Profile) {
464
- event.target.src = 'https://via.placeholder.com/300?text=' + (profile.name || 'User');
465
  }
466
 
467
- @HostListener('document:click', ['$event'])
468
- handleDocumentClick(event: MouseEvent) {
469
- const target = event.target as HTMLElement;
470
- if (!target.closest('.like-section')) {
471
- this.showLikedDropdown = false;
 
472
  }
 
 
 
 
 
 
 
473
  }
474
 
475
- @HostListener('document:keydown.escape')
476
- handleEscape() {
477
- this.closeModal();
478
- this.closeEditModal();
479
- this.closeChat();
 
 
 
 
 
 
 
 
 
480
  }
481
  }
 
1
+ import { Component, OnInit } from '@angular/core';
 
 
 
2
  import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+ import { MatchingService, ApiMatchResponse, ApiMatchItem } from '../matching.service';
5
+ import { ActivatedRoute } from '@angular/router';
6
 
7
+ interface Profile {
8
+ user_id: number;
9
+ name: string;
10
+ age?: number;
11
+ city?: string;
12
+ job?: string;
13
+ photo_url?: string;
14
  isOnline?: boolean;
15
  match_score?: number;
16
+ score?: number;
 
 
 
 
 
 
17
  gender?: string;
18
+ blue?: number;
19
+ green?: number;
20
+ yellow?: number;
21
+ red?: number;
22
+ explanations?: string[];
23
+ explanation_source?: string;
24
+ // Add fields from Marriage table
25
+ current_city?: string;
26
+ date_of_birth?: string;
27
+ job_role?: string;
28
+ employment_status?: string;
29
+ marital_status?: string;
30
+ education_level?: string;
31
  }
32
 
33
  interface ChatMessage {
 
34
  sender: 'me' | 'them';
35
+ text: string;
36
  time: string;
37
  }
38
 
39
  @Component({
40
  selector: 'app-matching-list',
 
 
41
  standalone: true,
42
+ imports: [CommonModule, FormsModule],
43
+ templateUrl: './matching-list.component.html',
44
+ styleUrls: ['./matching-list.component.css']
45
  })
46
  export class MatchingListComponent implements OnInit {
47
+ // User data
48
+ userId: number | null = null;
49
+ userName: string = 'User Name';
50
+ userCity: string = 'City';
51
+ userPhoto: string = 'https://cdn-icons-png.flaticon.com/512/3135/3135715.png';
52
+ userProfileCompletion: number = 75;
53
+ totalMatches: number = 0;
54
+
55
+ // Loading state
56
  loading = false;
57
  error: string | null = null;
58
 
59
+ // Matching data
60
+ result: ApiMatchResponse | null = null;
61
+ groupedMatches: { range: string; items: ApiMatchItem[] }[] = [];
62
+ displayedMatches: Profile[] = [];
 
 
63
 
64
+ // Filters and search
65
+ searchTerm: string = '';
66
+ availableLocations: string[] = ['New York', 'London', 'Tokyo', 'Paris', 'Sydney'];
67
+ availableAgeRanges: string[] = ['18-25', '26-35', '36-45', '46+'];
68
+ availableProfessions: string[] = ['Engineer', 'Doctor', 'Teacher', 'Designer', 'Developer'];
 
69
 
70
+ selectedRange: string = 'all';
71
+ availableRanges: string[] = ['all', '90-100', '80-89', '70-79', '60-69', 'below_60'];
72
+ mode: 'expectation' | 'character' = 'expectation';
73
+
74
+ // Modals and UI states
75
+ showProfileModal: boolean = false;
76
+ showEditModal: boolean = false;
77
+ showChatWindow: boolean = false;
78
+ showLikedDropdown: boolean = false;
79
+
80
+ // Selected profile data
 
 
 
 
81
  selectedProfile: Profile | null = null;
82
+ likedProfiles: Profile[] = [];
83
 
84
+ // Chat functionality
85
+ currentChatUser: Profile | null = null;
86
  chatMessages: ChatMessage[] = [];
87
+ newMessage: string = '';
88
+ showTyping: boolean = false;
89
 
90
+ // Edit profile
91
+ editName: string = '';
92
+ editCity: string = '';
 
 
 
 
93
 
94
+ // Store marriage profiles data
95
+ private marriageProfiles: Map<number, any> = new Map();
96
 
97
  constructor(
98
+ private matchingService: MatchingService,
99
+ private route: ActivatedRoute
100
  ) { }
101
 
102
+ ngOnInit(): void {
103
+ this.route.paramMap.subscribe((params) => {
104
+ const id = Number(params.get('id'));
105
+ this.userId = !isNaN(id) && id > 0 ? id : null;
106
+ if (this.userId) {
107
+ this.findMatches(this.userId);
108
+ this.loadUserProfile(this.userId);
109
+ } else {
110
+ this.error = 'No user ID available.';
111
+ }
112
+ });
113
  }
114
 
115
+ loadUserProfile(userId: number): void {
116
+ this.loading = true;
117
+
118
+ // Fetch user's marriage profile from backend
119
+ this.matchingService.getMarriageProfile(userId).subscribe({
120
+ next: (profile: any) => {
121
+ if (profile) {
122
+ // Update user panel with actual data
123
+ this.userName = profile.full_name || 'User Name';
124
+ this.userCity = profile.current_city || 'City';
125
+
126
+ // You can also set other user details if needed
127
+ console.log('User profile loaded:', profile);
128
+ } else {
129
+ console.warn('No profile found for user:', userId);
130
+ }
131
+ this.loading = false;
132
+ },
133
+ error: (err: any) => {
134
+ console.error('Failed to load user profile:', err);
135
+ // Fallback to default values if API fails
136
+ this.userName = 'User Name';
137
+ this.userCity = 'City';
138
+ this.loading = false;
139
+ }
140
+ });
141
  }
142
 
143
+ findMatches(userId: number): void {
 
144
  this.error = null;
145
+ this.result = null;
146
+ this.groupedMatches = [];
147
+ this.displayedMatches = [];
148
+ this.loading = true;
149
 
150
+ this.matchingService
151
+ .getMatches(userId, { role: 'marriage', limit: 10, excludeSelf: true })
152
+ .subscribe({
153
+ next: (res: ApiMatchResponse) => {
154
+ console.log('Backend Response:', res);
155
+ this.result = res;
156
+ this.groupedMatches = this.prepareGroups(res.matches, 'final_score');
157
+ this.loadMarriageProfiles().then(() => {
158
+ this.processMatchesForDisplay();
159
+ });
160
+ this.mode = 'expectation';
161
+ this.totalMatches = this.calculateTotalMatches(res.matches);
162
+ },
163
+ error: (err: any) => {
164
+ this.error = err?.error?.error || 'Failed to fetch matches.';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  this.loading = false;
166
  },
167
+ complete: () => (this.loading = false),
168
+ });
169
+ }
170
+
171
+ sortCharacterwise(): void {
172
+ if (!this.userId) return;
173
+ this.loading = true;
174
+ this.error = null;
175
+ this.result = null;
176
+ this.groupedMatches = [];
177
+ this.displayedMatches = [];
178
+
179
+ this.matchingService
180
+ .getCharacterwiseMatches(this.userId, { role: 'marriage', limit: 10, excludeSelf: true })
181
+ .subscribe({
182
+ next: (res: ApiMatchResponse) => {
183
+ this.result = res;
184
+ this.groupedMatches = this.prepareGroups(res.matches, 'score_color');
185
+ this.loadMarriageProfiles().then(() => {
186
+ this.processMatchesForDisplay();
187
+ });
188
+ this.mode = 'character';
189
+ this.totalMatches = this.calculateTotalMatches(res.matches);
190
+ },
191
  error: (err: any) => {
192
+ this.error = err?.error?.error || 'Failed to fetch characterwise matches.';
 
193
  this.loading = false;
194
+ },
195
+ complete: () => (this.loading = false),
196
  });
197
+ }
198
 
199
+ sortByExpectation(): void {
200
+ if (!this.userId) return;
201
+ this.loading = true;
202
+ this.error = null;
203
+ this.result = null;
204
+ this.groupedMatches = [];
205
+ this.displayedMatches = [];
206
+
207
+ this.matchingService
208
+ .getMatches(this.userId, { role: 'marriage', limit: 10, excludeSelf: true })
209
+ .subscribe({
210
+ next: (res: ApiMatchResponse) => {
211
+ this.result = res;
212
+ this.groupedMatches = this.prepareGroups(res.matches, 'final_score');
213
+ this.loadMarriageProfiles().then(() => {
214
+ this.processMatchesForDisplay();
215
+ });
216
+ this.mode = 'expectation';
217
+ this.totalMatches = this.calculateTotalMatches(res.matches);
218
+ },
219
+ error: (err: any) => {
220
+ this.error = err?.error?.error || 'Failed to fetch matches.';
221
+ this.loading = false;
222
+ },
223
+ complete: () => (this.loading = false),
224
+ });
225
  }
226
 
227
+ // Load marriage profiles from backend
228
+ private async loadMarriageProfiles(): Promise<void> {
229
+ if (!this.result) return;
 
 
 
230
 
231
+ // Collect all user IDs from matches
232
+ const userIds: number[] = [];
233
+ this.groupedMatches.forEach(group => {
234
+ group.items.forEach(item => {
235
+ userIds.push(item.user_id);
236
+ });
237
+ });
238
+
239
+ // Fetch marriage profiles for all matched users
240
+ for (const userId of userIds) {
241
+ if (!this.marriageProfiles.has(userId)) {
242
+ try {
243
+ const profile = await this.fetchMarriageProfile(userId);
244
+ if (profile) {
245
+ this.marriageProfiles.set(userId, profile);
246
+ }
247
+ } catch (error) {
248
+ console.error(`Failed to fetch profile for user ${userId}:`, error);
249
+ }
250
+ }
251
  }
252
+ }
253
 
254
+ // Fetch individual marriage profile
255
+ private fetchMarriageProfile(userId: number): Promise<any> {
256
+ return new Promise((resolve, reject) => {
257
+ this.matchingService.getMarriageProfile(userId).subscribe({
258
+ next: (profile: any) => resolve(profile),
259
+ error: (err: any) => reject(err)
260
+ });
261
+ });
262
  }
263
 
264
+ public processMatchesForDisplay(): void {
265
+ this.displayedMatches = [];
266
 
267
+ this.groupedMatches.forEach(group => {
268
+ group.items.forEach(item => {
269
+ const marriageProfile = this.marriageProfiles.get(item.user_id);
 
 
 
 
 
 
 
270
 
271
+ // Calculate age from date_of_birth
272
+ let age: number | undefined;
273
+ if (marriageProfile?.date_of_birth) {
274
+ age = this.calculateAge(marriageProfile.date_of_birth);
275
+ }
 
276
 
277
+ const profile: Profile = {
278
+ user_id: item.user_id,
279
+ name: item.name || 'Unknown',
280
+ age: age,
281
+ city: marriageProfile?.current_city || 'Unknown',
282
+ job: marriageProfile?.job_role || marriageProfile?.employment_status || 'Not specified',
283
+ photo_url: this.getRandomPhoto(), // Keep random photos for demo
284
+ isOnline: Math.random() > 0.5,
285
+ match_score: item.final_score || item.score_color || 0,
286
+ score: item.final_score || item.score_color || 0,
287
+ gender: item.gender,
288
+ blue: item.blue,
289
+ green: item.green,
290
+ yellow: item.yellow,
291
+ red: item.red,
292
+ explanations: item.explanations,
293
+ explanation_source: item.explanation_source,
294
+ // Include additional fields for modal display
295
+ current_city: marriageProfile?.current_city,
296
+ date_of_birth: marriageProfile?.date_of_birth,
297
+ job_role: marriageProfile?.job_role,
298
+ employment_status: marriageProfile?.employment_status,
299
+ marital_status: marriageProfile?.marital_status,
300
+ education_level: marriageProfile?.education_level
301
+ };
302
+ this.displayedMatches.push(profile);
303
  });
304
+ });
305
+ }
 
 
 
 
 
 
306
 
307
+ // Calculate age from date of birth
308
+ private calculateAge(dateOfBirth: string): number {
309
+ try {
310
+ const birthDate = new Date(dateOfBirth);
311
+ const today = new Date();
312
+ let age = today.getFullYear() - birthDate.getFullYear();
313
+ const monthDiff = today.getMonth() - birthDate.getMonth();
314
 
315
+ if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
316
+ age--;
 
 
 
 
 
 
 
 
 
317
  }
 
318
 
319
+ return age;
320
+ } catch (error) {
321
+ console.error('Error calculating age:', error);
322
+ return Math.floor(Math.random() * 20) + 25; // Fallback to random age
323
+ }
324
+ }
325
+
326
+ private calculateTotalMatches(matches: { [range: string]: ApiMatchItem[] }): number {
327
+ return Object.values(matches).reduce((total, group) => total + group.length, 0);
328
  }
329
 
330
+ private prepareGroups(matches: ApiMatchResponse['matches'], sortKey: keyof Pick<ApiMatchItem, 'score_color' | 'final_score'>) {
331
+ const ordered: { range: string; items: ApiMatchItem[] }[] = [];
332
+ const order = ['90-100', '80-89', '70-79', '60-69', 'below_60'];
333
+
334
+ for (const range of Object.keys(matches)) {
335
+ const items = matches[range] || [];
336
+ ordered.push({
337
+ range,
338
+ items: [...items].sort((a, b) => (Number(b[sortKey]) || 0) - (Number(a[sortKey]) || 0)),
339
+ });
340
+ }
341
 
342
+ ordered.sort((a, b) => order.indexOf(a.range) - order.indexOf(b.range));
343
+ return ordered;
344
  }
345
 
346
+ // Filter methods
347
+ onSearchChange(): void {
348
+ // Implement search logic
349
+ console.log('Search term:', this.searchTerm);
350
  }
351
 
352
+ onLocationFilterChange(event: any): void {
353
+ console.log('Location filter:', event.target.value);
 
354
  }
355
 
356
+ onAgeFilterChange(event: any): void {
357
+ console.log('Age filter:', event.target.value);
 
358
  }
359
 
360
+ onProfessionFilterChange(event: any): void {
361
+ console.log('Profession filter:', event.target.value);
 
362
  }
363
 
364
+ onScoreFilterChange(event: any): void {
365
+ console.log('Score filter:', event.target.value);
 
366
  }
367
 
368
+ onSortChange(event: any): void {
369
+ console.log('Sort by:', event.target.value);
 
370
  }
371
 
372
+ applyFilters(): void {
373
+ // Implement filter logic
374
+ console.log('Applying filters');
375
+ }
376
+
377
+ // Profile methods
378
+ openProfileModal(profile: Profile): void {
379
  this.selectedProfile = profile;
380
  this.showProfileModal = true;
381
  }
382
 
383
+ closeModal(): void {
384
  this.showProfileModal = false;
385
  this.selectedProfile = null;
386
  }
387
 
388
+ openEditModal(): void {
389
+ this.editName = this.userName;
390
+ this.editCity = this.userCity;
391
+ this.showEditModal = true;
392
  }
393
 
394
+ closeEditModal(): void {
395
+ this.showEditModal = false;
396
+ }
397
 
398
+ saveProfile(): void {
399
+ this.userName = this.editName;
400
+ this.userCity = this.editCity;
401
+ this.closeEditModal();
402
+ // In a real app, you would call an API to update the user profile here
403
+ console.log('Profile updated:', { name: this.userName, city: this.userCity });
404
+ }
405
+
406
+ // Like functionality
407
+ toggleLike(profile: Profile): void {
408
+ const index = this.likedProfiles.findIndex(p => p.user_id === profile.user_id);
409
  if (index > -1) {
410
  this.likedProfiles.splice(index, 1);
411
  } else {
412
  this.likedProfiles.push(profile);
 
413
  }
414
  }
415
 
 
417
  return this.likedProfiles.some(p => p.user_id === profile.user_id);
418
  }
419
 
420
+ toggleLikedDropdown(): void {
 
 
 
 
 
421
  this.showLikedDropdown = !this.showLikedDropdown;
422
  }
423
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  // Chat functionality
425
+ openChat(profile: Profile): void {
426
  this.currentChatUser = profile;
427
+ this.chatMessages = [
428
+ { sender: 'them', text: 'Hello! How are you?', time: '10:00 AM' },
429
+ { sender: 'me', text: 'I\'m good, thanks! How about you?', time: '10:01 AM' }
430
+ ];
431
  this.showChatWindow = true;
 
 
 
 
432
  }
433
 
434
+ closeChat(): void {
435
  this.showChatWindow = false;
436
  this.currentChatUser = null;
437
  this.chatMessages = [];
438
  }
439
 
440
+ sendMessage(): void {
441
+ if (this.newMessage.trim()) {
442
+ const now = new Date();
443
+ const timeString = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
444
+
445
+ this.chatMessages.push({
446
+ sender: 'me',
447
+ text: this.newMessage,
448
+ time: timeString
449
+ });
450
+
451
+ this.newMessage = '';
452
+
453
+ // Simulate reply
454
+ setTimeout(() => {
455
+ this.showTyping = true;
456
+ setTimeout(() => {
457
+ this.showTyping = false;
458
+ this.chatMessages.push({
459
+ sender: 'them',
460
+ text: 'That\'s interesting! Tell me more.',
461
+ time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
462
+ });
463
+ }, 2000);
464
+ }, 1000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
  }
466
  }
467
 
468
+ typingIndicator(): void {
469
+ // Could implement typing indicator logic here
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  }
471
 
472
+ // Utility methods
473
+ handleImageError(event: any, profile: Profile): void {
474
+ event.target.src = 'https://cdn-icons-png.flaticon.com/512/3135/3135715.png';
475
  }
476
 
477
+ getCompatibilityReport(profile: Profile): string {
478
+ if (!profile.explanations || profile.explanations.length === 0) {
479
+ return `<div class="compatibility-report">
480
+ <p>Based on your personality traits and expectations, you have a <strong>${profile.match_score}% compatibility</strong> with ${profile.name}.</p>
481
+ <p>This match considers both character compatibility and expectation alignment.</p>
482
+ </div>`;
483
  }
484
+
485
+ return `<div class="compatibility-report">
486
+ <p><strong>Compatibility Analysis:</strong></p>
487
+ <ul>
488
+ ${profile.explanations.map(exp => `<li>${exp}</li>`).join('')}
489
+ </ul>
490
+ </div>`;
491
  }
492
 
493
+ get filteredGroups() {
494
+ if (this.selectedRange === 'all') return this.groupedMatches;
495
+ return this.groupedMatches.filter((g) => g.range === this.selectedRange);
496
+ }
497
+
498
+ // Helper methods for demo data (only for photos)
499
+ private getRandomPhoto(): string {
500
+ const photos = [
501
+ 'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=150&h=150&fit=crop&crop=face',
502
+ 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150&h=150&fit=crop&crop=face',
503
+ 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&h=150&fit=crop&crop=face',
504
+ 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=150&h=150&fit=crop&crop=face'
505
+ ];
506
+ return photos[Math.floor(Math.random() * photos.length)];
507
  }
508
  }
src/app/matching.service.ts CHANGED
@@ -11,20 +11,19 @@ export interface ApiMatchItem {
11
  green: number;
12
  yellow: number;
13
  red: number;
14
- score: number;
15
- created_at?: string | null;
 
 
16
  explanations?: string[];
17
- // Optional display fields that may not come from API but used in templates
18
- city?: string | null;
19
- age?: number | null;
20
- job?: string | null;
21
- isOnline?: boolean; // optional presence indicator for UI
22
- photo_url?: string; // optional photo URL for profile image
23
  }
24
 
25
  export interface ApiMatchResponse {
26
  input_user: {
27
  user_id: number;
 
 
28
  role?: string | null;
29
  blue: number;
30
  green: number;
@@ -32,20 +31,22 @@ export interface ApiMatchResponse {
32
  red: number;
33
  created_at?: string | null;
34
  };
35
- matches: ApiMatchItem[];
36
- count: number;
 
37
  }
38
 
39
  @Injectable({ providedIn: 'root' })
40
  export class MatchingService {
41
-
42
  private baseUrl: string;
43
 
44
  constructor(private http: HttpClient) {
45
- // Initialize baseUrl based on the current hostname
46
- this.baseUrl = typeof location !== 'undefined' && location.hostname.endsWith('hf.space')
47
- ? 'https://pykara-py-match-backend.hf.space/api'
48
- : 'http://localhost:5000/api';
 
 
49
  }
50
 
51
  getMatches(
@@ -55,11 +56,27 @@ export class MatchingService {
55
  let params = new HttpParams()
56
  .set('user_id', String(userId))
57
  .set('limit', String(opts?.limit ?? 10))
58
- .set('exclude_self', (opts?.excludeSelf ?? true) ? 'yes' : 'no');
 
59
 
60
- // 👇 Default to "marriage" if role is not explicitly passed
61
- params = params.set('role', opts?.role ?? 'marriage');
 
 
 
 
 
 
 
 
 
 
 
62
 
63
  return this.http.get<ApiMatchResponse>(`${this.baseUrl}/match`, { params });
64
  }
 
 
 
 
65
  }
 
11
  green: number;
12
  yellow: number;
13
  red: number;
14
+ score_color?: number;
15
+ score_expect?: number;
16
+ final_score?: number;
17
+ percentage?: number;
18
  explanations?: string[];
19
+ explanation_source?: string; // added for expectation vs character
 
 
 
 
 
20
  }
21
 
22
  export interface ApiMatchResponse {
23
  input_user: {
24
  user_id: number;
25
+ name?: string;
26
+ gender?: string;
27
  role?: string | null;
28
  blue: number;
29
  green: number;
 
31
  red: number;
32
  created_at?: string | null;
33
  };
34
+ matches: {
35
+ [range: string]: ApiMatchItem[];
36
+ };
37
  }
38
 
39
  @Injectable({ providedIn: 'root' })
40
  export class MatchingService {
 
41
  private baseUrl: string;
42
 
43
  constructor(private http: HttpClient) {
44
+ // Initialize baseUrl based on the current environment
45
+ if (typeof window !== 'undefined' && window.location.hostname.endsWith('hf.space')) {
46
+ this.baseUrl = 'https://pykara-py-match-backend.hf.space/api';
47
+ } else {
48
+ this.baseUrl = 'http://localhost:5000/api';
49
+ }
50
  }
51
 
52
  getMatches(
 
56
  let params = new HttpParams()
57
  .set('user_id', String(userId))
58
  .set('limit', String(opts?.limit ?? 10))
59
+ .set('exclude_self', (opts?.excludeSelf ?? true) ? 'yes' : 'no')
60
+ .set('role', opts?.role ?? 'marriage');
61
 
62
+ return this.http.get<ApiMatchResponse>(`${this.baseUrl}/match`, { params });
63
+ }
64
+
65
+ getCharacterwiseMatches(
66
+ userId: number,
67
+ options: { role?: string; limit?: number; excludeSelf?: boolean } = {}
68
+ ): Observable<ApiMatchResponse> {
69
+ let params = new HttpParams()
70
+ .set('user_id', String(userId))
71
+ .set('role', options.role ?? 'marriage')
72
+ .set('limit', String(options.limit ?? 10))
73
+ .set('exclude_self', (options.excludeSelf ?? true) ? 'yes' : 'no')
74
+ .set('characterwise', 'true'); // ✅ activates LLM logic in backend
75
 
76
  return this.http.get<ApiMatchResponse>(`${this.baseUrl}/match`, { params });
77
  }
78
+
79
+ getMarriageProfile(userId: number): Observable<any> {
80
+ return this.http.get<any>(`${this.baseUrl}/marriage-profile/${userId}`);
81
+ }
82
  }