bhoomika19 commited on
Commit
0ba6be4
·
verified ·
1 Parent(s): beb397e

Upload InformationGraph.js

Browse files
frontend/src/components/InformationGraph.js ADDED
@@ -0,0 +1,609 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import { Card, Row, Col, Spinner, Alert, Badge } from 'react-bootstrap';
3
+ import { FaUser, FaHeart, FaIdCard, FaStar } from 'react-icons/fa';
4
+ import { memoryAPI } from '../services/api';
5
+
6
+ const InformationGraph = () => {
7
+ const [userSummaries, setUserSummaries] = useState([]);
8
+ const [loading, setLoading] = useState(true);
9
+ const [error, setError] = useState(null);
10
+ const [isInitialized, setIsInitialized] = useState(false);
11
+ const [refreshTimeout, setRefreshTimeout] = useState(null);
12
+
13
+ useEffect(() => {
14
+ // Prevent duplicate calls in React StrictMode during development
15
+ if (!isInitialized) {
16
+ setIsInitialized(true);
17
+ fetchAcceptedInformation();
18
+ }
19
+
20
+ // Listen for memory status changes (approve/reject actions)
21
+ const handleMemoryStatusChange = (event) => {
22
+ console.log('🔄 Information Graph received memory status change:', event.detail);
23
+ console.log('📊 Refreshing Information Graph due to approve/reject action...');
24
+
25
+ // Clear any existing timeout to debounce rapid successive changes
26
+ if (refreshTimeout) {
27
+ clearTimeout(refreshTimeout);
28
+ }
29
+
30
+ // Set a small delay to batch multiple rapid changes
31
+ const newTimeout = setTimeout(() => {
32
+ fetchAcceptedInformation();
33
+ }, 500); // 500ms delay to batch multiple changes
34
+
35
+ setRefreshTimeout(newTimeout);
36
+ };
37
+
38
+ window.addEventListener('memoryStatusChanged', handleMemoryStatusChange);
39
+
40
+ // Cleanup event listener on component unmount
41
+ return () => {
42
+ window.removeEventListener('memoryStatusChanged', handleMemoryStatusChange);
43
+ // Clear any pending refresh timeout
44
+ if (refreshTimeout) {
45
+ clearTimeout(refreshTimeout);
46
+ }
47
+ };
48
+ }, [isInitialized]); // Remove fetchAcceptedInformation from dependency array
49
+
50
+ const fetchAcceptedInformation = async () => {
51
+ try {
52
+ setLoading(true);
53
+ setError(null);
54
+
55
+ // Get all users first
56
+ if (process.env.NODE_ENV === 'development') {
57
+ console.log('Fetching users...');
58
+ }
59
+ const usersResponse = await memoryAPI.getUsers();
60
+ const users = usersResponse.data; // This is already an array of user IDs
61
+ if (process.env.NODE_ENV === 'development') {
62
+ console.log('Users fetched:', users);
63
+ }
64
+
65
+ if (!users || users.length === 0) {
66
+ setError('No users found in the system');
67
+ return;
68
+ }
69
+
70
+ const summaries = [];
71
+
72
+ // For each user, get their accepted information
73
+ for (const userId of users) {
74
+ try {
75
+ if (process.env.NODE_ENV === 'development') {
76
+ console.log(`Fetching memory for user: ${userId}`);
77
+ }
78
+ const memoryResponse = await memoryAPI.getUserMemory(userId);
79
+ const concludedFacts = memoryResponse.data || []; // API returns array directly
80
+
81
+ // Filter only approved facts
82
+ const approvedFacts = concludedFacts.filter(fact => fact.status === 'approved');
83
+
84
+ console.log(`User ${userId} - Total facts: ${concludedFacts.length}, Approved facts: ${approvedFacts.length}`);
85
+ console.log(`User ${userId} - Layer 4+ approved facts:`, approvedFacts.filter(f => f.layer === 'Layer4'));
86
+
87
+ if (approvedFacts.length > 0) {
88
+ const summary = processUserFacts(userId, approvedFacts);
89
+ console.log(`User ${userId} - Final summary preferences:`, summary.preferences);
90
+ console.log(`User ${userId} - Final summary interests:`, summary.interests);
91
+ summaries.push(summary);
92
+ }
93
+ } catch (err) {
94
+ console.error(`Error fetching memory for user ${userId}:`, err);
95
+ // Continue with other users even if one fails
96
+ }
97
+ }
98
+
99
+ if (process.env.NODE_ENV === 'development') {
100
+ console.log('Final summaries:', summaries);
101
+ }
102
+ setUserSummaries(summaries);
103
+
104
+ if (summaries.length === 0) {
105
+ setError('No approved information found for any users');
106
+ }
107
+
108
+ } catch (err) {
109
+ setError(`Failed to load user information: ${err.message}`);
110
+ console.error('Error fetching user information:', err);
111
+ } finally {
112
+ setLoading(false);
113
+ }
114
+ };
115
+
116
+ const processUserFacts = (userId, facts) => {
117
+ console.log(`Processing ${facts.length} facts for ${userId}:`, facts);
118
+
119
+ // Create a dynamic structure to hold all fact types
120
+ const factsByType = {};
121
+ const mobileNumbers = [];
122
+ const emails = [];
123
+
124
+ facts.forEach(fact => {
125
+ const factType = fact.fact_type || 'unknown';
126
+ const conclusion = fact.conclusion || '';
127
+ const rawValue = fact.raw_value || '';
128
+
129
+ // Extract value after "is" from conclusion or use raw_value
130
+ let value = '';
131
+ if (rawValue && rawValue !== 'N/A') {
132
+ value = rawValue.trim();
133
+ } else {
134
+ // Extract from conclusion - find text after "is"
135
+ const match = conclusion.match(/.*\s+is\s+(.+)/i);
136
+ if (match) {
137
+ value = match[1].trim();
138
+ } else {
139
+ // Fallback - use the whole conclusion if no "is" pattern
140
+ value = conclusion.trim();
141
+ }
142
+ }
143
+
144
+ // Skip empty or meaningless values
145
+ if (!value || value === 'N/A' || value.length <= 1) {
146
+ return;
147
+ }
148
+
149
+ // Group by fact type for dynamic display
150
+ if (!factsByType[factType]) {
151
+ factsByType[factType] = [];
152
+ }
153
+
154
+ // Avoid duplicates
155
+ if (!factsByType[factType].includes(value)) {
156
+ factsByType[factType].push(value);
157
+ }
158
+
159
+ // Special handling for UI compatibility - populate existing structure
160
+ if (factType === 'phone_number' || factType === 'phone' || factType === 'mobile') {
161
+ if (!mobileNumbers.includes(value)) {
162
+ mobileNumbers.push(value);
163
+ }
164
+ }
165
+
166
+ if (factType === 'email') {
167
+ if (!emails.includes(value)) {
168
+ emails.push(value);
169
+ }
170
+ }
171
+ });
172
+
173
+ // Build the summary structure that matches existing UI expectations
174
+ const summary = {
175
+ userId,
176
+ basicInfo: {
177
+ userName: factsByType['name']?.[0] || '',
178
+ fullName: factsByType['full_name']?.[0] || '',
179
+ homeAddress: factsByType['address']?.[0] || factsByType['home_address']?.[0] || '',
180
+ officeAddress: factsByType['office_address']?.[0] || '',
181
+ dob: factsByType['date_of_birth']?.[0] || factsByType['dob']?.[0] || '',
182
+ gender: factsByType['gender']?.[0] || '',
183
+ bloodGroup: factsByType['blood_group']?.[0] || '',
184
+ mobileNumbers: mobileNumbers,
185
+ relationshipStatus: factsByType['relationship_status']?.[0] || factsByType['marital_status']?.[0] || '',
186
+ nationality: factsByType['nationality']?.[0] || '',
187
+ placeOfBirth: factsByType['place_of_birth']?.[0] || '',
188
+ email: emails[0] || '',
189
+ additionalEmails: emails.slice(1)
190
+ },
191
+ spouse: {
192
+ fullName: factsByType['spouse_name']?.[0] || factsByType['spouse']?.[0] || factsByType['wife']?.[0] || factsByType['husband']?.[0] || '',
193
+ homeAddress: '',
194
+ officeAddress: '',
195
+ dob: '',
196
+ gender: '',
197
+ bloodGroup: '',
198
+ mobileNumbers: [],
199
+ relationshipStatus: '',
200
+ nationality: '',
201
+ placeOfBirth: ''
202
+ },
203
+ documents: {
204
+ aadhaarCard: factsByType['aadhaar']?.[0] || '',
205
+ panCard: factsByType['pan']?.[0] || '',
206
+ drivingLicense: factsByType['driving_license']?.[0] || '',
207
+ voterID: factsByType['voter_id']?.[0] || '',
208
+ passport: factsByType['passport']?.[0] || '',
209
+ birthCertificate: '',
210
+ healthInsurance: '',
211
+ marriageCertificate: '',
212
+ rentAgreement: '',
213
+ utilityBill: ''
214
+ },
215
+ relations: {
216
+ mother: { fullName: factsByType['mother']?.[0] || '' },
217
+ father: { fullName: factsByType['father']?.[0] || '' },
218
+ sibling: { fullName: factsByType['sibling']?.[0] || '' },
219
+ flatmate: { fullName: factsByType['flatmate']?.[0] || '' },
220
+ partner: { fullName: factsByType['partner']?.[0] || '' },
221
+ friend: { fullName: factsByType['friend']?.[0] || '' },
222
+ boss: { fullName: factsByType['boss']?.[0] || '' },
223
+ maid: { fullName: factsByType['maid']?.[0] || '' },
224
+ cook: { fullName: factsByType['cook']?.[0] || '' },
225
+ driver: { fullName: factsByType['driver']?.[0] || '' }
226
+ },
227
+ // Dynamic preferences - include ALL fact types that aren't in basic categories
228
+ preferences: [],
229
+ interests: [],
230
+ // Dynamic fact display - show ALL fact types
231
+ allFacts: factsByType,
232
+ financial: factsByType['credit_card_last_digits'] ? {
233
+ creditCardLastDigits: factsByType['credit_card_last_digits'][0]
234
+ } : null,
235
+ stats: {
236
+ totalFacts: facts.length,
237
+ layerDistribution: { Layer1: 0, Layer2: 0, Layer3: 0, Layer4: 0 }
238
+ }
239
+ };
240
+
241
+ // Categorize fact types dynamically
242
+ const basicInfoTypes = ['name', 'full_name', 'address', 'home_address', 'office_address', 'date_of_birth', 'dob', 'gender', 'blood_group', 'phone_number', 'phone', 'mobile', 'email', 'relationship_status', 'marital_status', 'nationality', 'place_of_birth'];
243
+ const documentTypes = ['aadhaar', 'pan', 'driving_license', 'voter_id', 'passport'];
244
+ const relationTypes = ['spouse_name', 'spouse', 'wife', 'husband', 'mother', 'father', 'sibling', 'flatmate', 'partner', 'friend', 'boss', 'maid', 'cook', 'driver'];
245
+ const financialTypes = ['credit_card_last_digits'];
246
+
247
+ // Only add fact types that contain "preference" or "interest" to the dedicated sections
248
+ Object.keys(factsByType).forEach(factType => {
249
+ if (factType.includes('preference') && !basicInfoTypes.includes(factType) &&
250
+ !documentTypes.includes(factType) && !relationTypes.includes(factType) &&
251
+ !financialTypes.includes(factType)) {
252
+
253
+ factsByType[factType].forEach(value => {
254
+ if (!summary.preferences.includes(value)) {
255
+ summary.preferences.push(value);
256
+ }
257
+ });
258
+ } else if (factType.includes('interest') || factType.includes('hobby')) {
259
+ factsByType[factType].forEach(value => {
260
+ if (!summary.interests.includes(value)) {
261
+ summary.interests.push(value);
262
+ }
263
+ });
264
+ }
265
+ });
266
+
267
+ // Count layer distribution for existing UI
268
+ facts.forEach(fact => {
269
+ const layer = fact.layer;
270
+ if (summary.stats.layerDistribution[layer] !== undefined) {
271
+ summary.stats.layerDistribution[layer]++;
272
+ }
273
+ });
274
+
275
+ // Debug output to see what we extracted
276
+ console.log(`Processing user facts - Extracted preferences:`, summary.preferences);
277
+ console.log(`Processing user facts - Extracted interests:`, summary.interests);
278
+ console.log(`Processing user facts - All fact types:`, Object.keys(factsByType));
279
+
280
+ return summary;
281
+ };
282
+ const renderBasicInformation = (basicInfo) => {
283
+ const fields = [
284
+ { label: 'User Name', value: basicInfo.userName },
285
+ { label: 'Full Name', value: basicInfo.fullName },
286
+ { label: 'Home Address', value: basicInfo.homeAddress },
287
+ { label: 'Office Address', value: basicInfo.officeAddress },
288
+ { label: 'Email', value: basicInfo.email },
289
+ { label: 'Additional Emails', value: basicInfo.additionalEmails?.join(', ') },
290
+ { label: 'Date of Birth', value: basicInfo.dob },
291
+ { label: 'Gender', value: basicInfo.gender },
292
+ { label: 'Blood Group', value: basicInfo.bloodGroup },
293
+ { label: 'Mobile Numbers', value: basicInfo.mobileNumbers.join(', ') },
294
+ { label: 'Relationship Status', value: basicInfo.relationshipStatus },
295
+ { label: 'Nationality', value: basicInfo.nationality },
296
+ { label: 'Place of Birth', value: basicInfo.placeOfBirth }
297
+ ];
298
+
299
+ // Filter out empty values and "N/A" values
300
+ const visibleFields = fields.filter(field => field.value && field.value !== 'N/A');
301
+
302
+ if (visibleFields.length === 0) return null;
303
+
304
+ return (
305
+ <div className="info-category">
306
+ <div className="info-category-title">
307
+ <FaUser className="me-2" />
308
+ Basic Information
309
+ </div>
310
+ {visibleFields.map((field, index) => (
311
+ <div key={index} className="info-item">
312
+ <strong>{field.label}:</strong> {field.value}
313
+ </div>
314
+ ))}
315
+ </div>
316
+ );
317
+ };
318
+
319
+ const renderSpouseInformation = (spouse) => {
320
+ const fields = [
321
+ { label: 'Full Name', value: spouse.fullName },
322
+ { label: 'Home Address', value: spouse.homeAddress },
323
+ { label: 'Office Address', value: spouse.officeAddress },
324
+ { label: 'Date of Birth', value: spouse.dob },
325
+ { label: 'Gender', value: spouse.gender },
326
+ { label: 'Blood Group', value: spouse.bloodGroup },
327
+ { label: 'Mobile Numbers', value: spouse.mobileNumbers?.join(', ') },
328
+ { label: 'Relationship Status', value: spouse.relationshipStatus },
329
+ { label: 'Nationality', value: spouse.nationality },
330
+ { label: 'Place of Birth', value: spouse.placeOfBirth }
331
+ ];
332
+
333
+ // Filter out empty values and "N/A" values
334
+ const visibleFields = fields.filter(field => field.value && field.value !== 'N/A');
335
+
336
+ if (visibleFields.length === 0) return null;
337
+
338
+ return (
339
+ <div className="info-category">
340
+ <div className="info-category-title">
341
+ <FaHeart className="me-2" />
342
+ Spouse Information
343
+ </div>
344
+ {visibleFields.map((field, index) => (
345
+ <div key={index} className="info-item">
346
+ <strong>{field.label}:</strong> {field.value}
347
+ </div>
348
+ ))}
349
+ </div>
350
+ );
351
+ };
352
+
353
+ const renderDocuments = (documents) => {
354
+ const docFields = [
355
+ { label: 'Aadhaar Card', value: documents.aadhaarCard },
356
+ { label: 'PAN Card', value: documents.panCard },
357
+ { label: 'Driving License', value: documents.drivingLicense },
358
+ { label: 'Voter ID', value: documents.voterID },
359
+ { label: 'Birth Certificate', value: documents.birthCertificate },
360
+ { label: 'Health Insurance', value: documents.healthInsurance },
361
+ { label: 'Marriage Certificate', value: documents.marriageCertificate },
362
+ { label: 'Rent Agreement', value: documents.rentAgreement },
363
+ { label: 'Utility Bill', value: documents.utilityBill },
364
+ { label: 'Passport', value: documents.passport }
365
+ ];
366
+
367
+ // Filter out empty values and "N/A" values
368
+ const visibleDocs = docFields.filter(doc => doc.value && doc.value !== 'N/A');
369
+
370
+ if (visibleDocs.length === 0) return null;
371
+
372
+ return (
373
+ <div className="info-category">
374
+ <div className="info-category-title">
375
+ <FaIdCard className="me-2" />
376
+ Documents
377
+ </div>
378
+ {visibleDocs.map((doc, index) => (
379
+ <div key={index} className="info-item">
380
+ <strong>{doc.label}:</strong> {doc.value}
381
+ </div>
382
+ ))}
383
+ </div>
384
+ );
385
+ };
386
+
387
+ const renderRelations = (relations) => {
388
+ const relationTypes = [
389
+ { key: 'mother', label: 'Mother' },
390
+ { key: 'father', label: 'Father' },
391
+ { key: 'sibling', label: 'Sibling' },
392
+ { key: 'flatmate', label: 'Flatmate' },
393
+ { key: 'partner', label: 'Partner' },
394
+ { key: 'friend', label: 'Friend' },
395
+ { key: 'boss', label: 'Boss' },
396
+ { key: 'maid', label: 'Maid' },
397
+ { key: 'cook', label: 'Cook' },
398
+ { key: 'driver', label: 'Driver' }
399
+ ];
400
+
401
+ const visibleRelations = relationTypes.filter(rel =>
402
+ relations[rel.key] && relations[rel.key].fullName && relations[rel.key].fullName !== 'N/A'
403
+ );
404
+
405
+ if (visibleRelations.length === 0) return null;
406
+
407
+ return (
408
+ <div className="info-category">
409
+ <div className="info-category-title">
410
+ <FaHeart className="me-2" />
411
+ Relations
412
+ </div>
413
+ {visibleRelations.map((rel, index) => (
414
+ <div key={index} className="info-item">
415
+ <strong>{rel.label}:</strong> {relations[rel.key].fullName}
416
+ </div>
417
+ ))}
418
+ </div>
419
+ );
420
+ };
421
+
422
+ const renderPreferences = (preferences) => {
423
+ if (!preferences || preferences.length === 0) return null;
424
+
425
+ return (
426
+ <div className="info-category">
427
+ <div className="info-category-title">
428
+ <FaHeart className="me-2" />
429
+ Preferences
430
+ </div>
431
+ {preferences.map((preference, index) => (
432
+ <div key={index} className="info-item">
433
+ • {preference}
434
+ </div>
435
+ ))}
436
+ </div>
437
+ );
438
+ };
439
+
440
+ const renderInterests = (interests) => {
441
+ if (!interests || interests.length === 0) return null;
442
+
443
+ return (
444
+ <div className="info-category">
445
+ <div className="info-category-title">
446
+ <FaStar className="me-2" />
447
+ Interests & Hobbies
448
+ </div>
449
+ {interests.map((interest, index) => (
450
+ <div key={index} className="info-item">
451
+ • {interest}
452
+ </div>
453
+ ))}
454
+ </div>
455
+ );
456
+ };
457
+
458
+ const renderAllFacts = (allFacts) => {
459
+ if (!allFacts || Object.keys(allFacts).length === 0) return null;
460
+
461
+ // Filter out fact types that are already displayed in other sections
462
+ const basicInfoTypes = ['name', 'full_name', 'address', 'home_address', 'office_address', 'date_of_birth', 'dob', 'gender', 'blood_group', 'phone_number', 'phone', 'mobile', 'email', 'relationship_status', 'marital_status', 'nationality', 'place_of_birth'];
463
+ const documentTypes = ['aadhaar', 'pan', 'driving_license', 'voter_id', 'passport'];
464
+ const relationTypes = ['spouse_name', 'spouse', 'wife', 'husband', 'mother', 'father', 'sibling', 'flatmate', 'partner', 'friend', 'boss', 'maid', 'cook', 'driver'];
465
+ const financialTypes = ['credit_card_last_digits'];
466
+ const preferenceTypes = []; // Collect all preference-related fact types
467
+ const interestTypes = []; // Collect all interest-related fact types
468
+
469
+ // Identify preference and interest fact types
470
+ Object.keys(allFacts).forEach(factType => {
471
+ if (factType.includes('preference')) {
472
+ preferenceTypes.push(factType);
473
+ } else if (factType.includes('interest') || factType.includes('hobby')) {
474
+ interestTypes.push(factType);
475
+ }
476
+ });
477
+
478
+ const dynamicFacts = {};
479
+ Object.keys(allFacts).forEach(factType => {
480
+ if (!basicInfoTypes.includes(factType) &&
481
+ !documentTypes.includes(factType) &&
482
+ !relationTypes.includes(factType) &&
483
+ !financialTypes.includes(factType) &&
484
+ !preferenceTypes.includes(factType) &&
485
+ !interestTypes.includes(factType)) {
486
+ dynamicFacts[factType] = allFacts[factType];
487
+ }
488
+ });
489
+
490
+ if (Object.keys(dynamicFacts).length === 0) return null;
491
+
492
+ return (
493
+ <div className="info-category mt-3">
494
+ <div className="info-category-title">
495
+ <FaStar className="me-2" />
496
+ Additional Information
497
+ </div>
498
+ {Object.entries(dynamicFacts).map(([factType, values]) => (
499
+ <div key={factType} className="mb-2">
500
+ <strong>{factType.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}:</strong>
501
+ <div>
502
+ {values.map((value, index) => (
503
+ <div key={index} className="info-item">
504
+ • {value}
505
+ </div>
506
+ ))}
507
+ </div>
508
+ </div>
509
+ ))}
510
+ </div>
511
+ );
512
+ };
513
+
514
+ if (loading) {
515
+ return (
516
+ <div className="info-graph-container">
517
+ <div className="loading-spinner">
518
+ <Spinner animation="border" variant="primary" />
519
+ </div>
520
+ </div>
521
+ );
522
+ }
523
+
524
+ if (error) {
525
+ return (
526
+ <div className="info-graph-container">
527
+ <Alert variant="danger">{error}</Alert>
528
+ </div>
529
+ );
530
+ }
531
+
532
+ return (
533
+ <div className="info-graph-container">
534
+ <h2 className="info-graph-header">
535
+ <FaUser className="me-3" />
536
+ Information Graph
537
+ {loading && (
538
+ <small className="ms-2 text-muted">
539
+ <Spinner animation="border" size="sm" className="me-1" />
540
+ Refreshing...
541
+ </small>
542
+ )}
543
+ </h2>
544
+
545
+ {userSummaries.length === 0 ? (
546
+ <Alert variant="info" className="text-center">
547
+ No approved information available yet. Process some user data and approve facts to see them here.
548
+ </Alert>
549
+ ) : (
550
+ <Row>
551
+ {userSummaries.map((summary) => (
552
+ <Col lg={12} xl={6} key={summary.userId} className="mb-4">
553
+ <Card className="user-summary-card h-100">
554
+ <Card.Body>
555
+ <div className="d-flex justify-content-between align-items-center mb-3">
556
+ <h5 className="user-name-header mb-0">
557
+ {(summary.basicInfo.fullName && summary.basicInfo.fullName !== 'N/A') ? summary.basicInfo.fullName :
558
+ (summary.basicInfo.userName && summary.basicInfo.userName !== 'N/A') ? summary.basicInfo.userName :
559
+ summary.userId}
560
+ </h5>
561
+ <Badge bg="primary" className="rounded-pill">
562
+ {summary.stats.totalFacts} facts
563
+ </Badge>
564
+ </div>
565
+
566
+ {/* Basic Information */}
567
+ {renderBasicInformation(summary.basicInfo)}
568
+
569
+ {/* Spouse Information */}
570
+ {renderSpouseInformation(summary.spouse)}
571
+
572
+ {/* Documents */}
573
+ {renderDocuments(summary.documents)}
574
+
575
+ {/* Financial Information */}
576
+ {summary.financial && (
577
+ <div className="info-category mt-3">
578
+ <div className="info-category-title">Financial Information</div>
579
+ {summary.financial.creditCardLastDigits && (
580
+ <div className="info-item">
581
+ <span className="info-label">Credit Card Last 4 Digits:</span>
582
+ <span className="info-value">****{summary.financial.creditCardLastDigits}</span>
583
+ </div>
584
+ )}
585
+ </div>
586
+ )}
587
+
588
+ {/* Relations */}
589
+ {renderRelations(summary.relations)}
590
+
591
+ {/* Preferences */}
592
+ {renderPreferences(summary.preferences)}
593
+
594
+ {/* Interests & Hobbies */}
595
+ {renderInterests(summary.interests)}
596
+
597
+ {/* All Dynamic Facts */}
598
+ {renderAllFacts(summary.allFacts)}
599
+ </Card.Body>
600
+ </Card>
601
+ </Col>
602
+ ))}
603
+ </Row>
604
+ )}
605
+ </div>
606
+ );
607
+ };
608
+
609
+ export default InformationGraph;