sksameermujahid commited on
Commit
c061318
Β·
verified Β·
1 Parent(s): bb8ecb3

Upload 19 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Python 3.10 slim image
2
+ FROM python:3.10-slim
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies
8
+ RUN apt-get update && apt-get install -y \
9
+ gcc \
10
+ g++ \
11
+ curl \
12
+ && rm -rf /var/lib/apt/lists/*
13
+
14
+ # Copy requirements first for better caching
15
+ COPY requirements.txt .
16
+
17
+ # Install Python dependencies
18
+ RUN pip install --no-cache-dir -r requirements.txt
19
+
20
+ # Create non-root user
21
+ RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
22
+
23
+ # Copy application files
24
+ COPY . .
25
+
26
+ # Create necessary directories with proper permissions
27
+ RUN mkdir -p templates property_db /tmp/hf_cache /tmp/chromadb_properties
28
+ RUN mkdir -p /tmp/hf_cache/transformers /tmp/hf_cache/datasets /tmp/hf_cache/hub /tmp/hf_cache/chroma /tmp/hf_cache/onnx /tmp/hf_cache/sentence_transformers
29
+ RUN chmod -R 755 /tmp/hf_cache /tmp/chromadb_properties
30
+ RUN chown -R appuser:appuser /tmp/hf_cache /tmp/chromadb_properties
31
+
32
+ # Set environment variables
33
+ ENV PYTHONPATH=/app
34
+ ENV FLASK_APP=app.py
35
+ ENV PORT=7860
36
+ ENV HF_HOME=/tmp/hf_cache
37
+ ENV TRANSFORMERS_CACHE=/tmp/hf_cache/transformers
38
+ ENV HF_DATASETS_CACHE=/tmp/hf_cache/datasets
39
+ ENV HF_HUB_CACHE=/tmp/hf_cache/hub
40
+ ENV CHROMA_CACHE_DIR=/tmp/hf_cache/chroma
41
+ ENV ONNXRUNTIME_CACHE_DIR=/tmp/hf_cache/onnx
42
+ ENV SENTENCE_TRANSFORMERS_HOME=/tmp/hf_cache/sentence_transformers
43
+ ENV CHROMADB_PERSIST_DIRECTORY=/tmp/chromadb_properties
44
+ ENV CHROMADB_IS_PERSISTENT=true
45
+
46
+ # Expose port (Hugging Face Spaces uses 7860)
47
+ EXPOSE 7860
48
+
49
+ # Switch to non-root user
50
+ USER appuser
51
+
52
+ # Health check
53
+ HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
54
+ CMD curl -f http://localhost:7860/api/health || exit 1
55
+
56
+ # Run the application
57
+ CMD ["python", "app.py"]
ai_recommendation_engine.py ADDED
The diff for this file is too large to render. See raw diff
 
api_service_enhanced.py ADDED
The diff for this file is too large to render. See raw diff
 
app.py ADDED
@@ -0,0 +1,358 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Unified AI Lead Analysis System
4
+ Single port (7860) solution with API, Frontend, and Email features
5
+ Enhanced with 24-hour background property fetching and peak time email automation
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import logging
11
+ import threading
12
+ import schedule
13
+ import time
14
+ from datetime import datetime, timedelta
15
+
16
+ # Configure logging
17
+ logging.basicConfig(level=logging.INFO)
18
+ logger = logging.getLogger(__name__)
19
+
20
+ class UnifiedAISystem:
21
+ def __init__(self):
22
+ """Initialize the unified AI system with background services"""
23
+ self.background_thread = None
24
+ self.email_thread = None
25
+ self.is_running = False
26
+
27
+ # Initialize AI engine
28
+ try:
29
+ from ai_recommendation_engine import AIRecommendationEngine
30
+ self.ai_engine = AIRecommendationEngine()
31
+ logger.info("βœ… AI Recommendation Engine initialized")
32
+ except Exception as e:
33
+ logger.error(f"❌ Failed to initialize AI engine: {e}")
34
+ self.ai_engine = None
35
+
36
+ def start_background_services(self):
37
+ """Start background services for property fetching and email automation"""
38
+ logger.info("πŸš€ Starting background services...")
39
+
40
+ # Start background property fetching thread
41
+ self.background_thread = threading.Thread(target=self._run_background_services, daemon=True)
42
+ self.background_thread.start()
43
+
44
+ # Start email automation thread
45
+ self.email_thread = threading.Thread(target=self._run_email_automation, daemon=True)
46
+ self.email_thread.start()
47
+
48
+ logger.info("βœ… Background services started successfully")
49
+
50
+ def _run_background_services(self):
51
+ """Run background services including property fetching"""
52
+ logger.info("πŸ”„ Background services thread started")
53
+
54
+ # Schedule property fetching every 24 hours
55
+ schedule.every(24).hours.do(self._fetch_properties_background)
56
+
57
+ # Run initial property fetch
58
+ logger.info("πŸš€ Running initial property fetch...")
59
+ self._fetch_properties_background()
60
+
61
+ # Keep the scheduler running
62
+ while self.is_running:
63
+ schedule.run_pending()
64
+ time.sleep(60) # Check every minute
65
+
66
+ def _fetch_properties_background(self):
67
+ """Fetch properties in background without blocking the main application"""
68
+ try:
69
+ logger.info("πŸ”„ Starting background property fetch...")
70
+
71
+ if self.ai_engine:
72
+ # Run property fetching in a separate thread to avoid blocking
73
+ fetch_thread = threading.Thread(target=self._execute_property_fetch, daemon=True)
74
+ fetch_thread.start()
75
+ logger.info("βœ… Background property fetch initiated")
76
+ else:
77
+ logger.warning("⚠️ AI engine not available for property fetching")
78
+
79
+ except Exception as e:
80
+ logger.error(f"❌ Error in background property fetch: {e}")
81
+
82
+ def _execute_property_fetch(self):
83
+ """Execute the actual property fetching"""
84
+ try:
85
+ logger.info("πŸ“₯ Executing parallel property fetch...")
86
+ success = self.ai_engine.fetch_all_properties_parallel(
87
+ max_workers=10,
88
+ page_size=100,
89
+ max_pages=10
90
+ )
91
+
92
+ if success:
93
+ logger.info("βœ… Background property fetch completed successfully")
94
+ # Update cache timestamp
95
+ self._update_cache_timestamp()
96
+ else:
97
+ logger.warning("⚠️ Background property fetch completed with issues")
98
+
99
+ except Exception as e:
100
+ logger.error(f"❌ Error executing property fetch: {e}")
101
+
102
+ def _update_cache_timestamp(self):
103
+ """Update the cache timestamp file"""
104
+ try:
105
+ import os
106
+ cache_dir = 'cache'
107
+ os.makedirs(cache_dir, exist_ok=True)
108
+ timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
109
+ with open(f'{cache_dir}/property_cache_timestamp.txt', 'w') as f:
110
+ f.write(timestamp)
111
+ logger.info(f"πŸ“… Cache timestamp updated: {timestamp}")
112
+ except Exception as e:
113
+ logger.error(f"❌ Error updating cache timestamp: {e}")
114
+
115
+ def _run_email_automation(self):
116
+ """Run email automation services"""
117
+ logger.info("πŸ“§ Email automation thread started")
118
+
119
+ # Schedule peak time emails at 2 PM, 6 PM, and 10 PM daily based on user analysis
120
+ schedule.every().day.at("14:00").do(self._send_peak_time_emails_2pm)
121
+ schedule.every().day.at("18:00").do(self._send_peak_time_emails_6pm)
122
+ schedule.every().day.at("22:00").do(self._send_peak_time_emails_10pm)
123
+
124
+ # Also schedule a test email every hour for testing purposes
125
+ schedule.every().hour.do(self._send_test_emails)
126
+
127
+ logger.info("πŸ“… Scheduled peak time emails: 2 PM, 6 PM, and 10 PM daily")
128
+ logger.info("πŸ“§ Scheduled test emails: Every hour for testing")
129
+
130
+ # Keep the scheduler running
131
+ while self.is_running:
132
+ schedule.run_pending()
133
+ time.sleep(60) # Check every minute
134
+
135
+ def _send_peak_time_emails_2pm(self):
136
+ """Send peak time emails at 2 PM based on user analysis"""
137
+ try:
138
+ logger.info("πŸ“§ Starting 2 PM peak time email automation based on user analysis...")
139
+
140
+ # Import email service
141
+ from email_automation_service import EmailAutomationService
142
+ email_service = EmailAutomationService()
143
+
144
+ # Send Multi-AI emails for all customers
145
+ customers = self._get_all_customers()
146
+ for customer_id in customers:
147
+ try:
148
+ analysis_data = self._get_customer_analysis(customer_id)
149
+ if analysis_data:
150
+ email_service.send_multi_ai_emails(customer_id, analysis_data)
151
+ logger.info(f"βœ… Multi-AI emails sent for customer {customer_id}")
152
+ except Exception as e:
153
+ logger.error(f"❌ Error sending Multi-AI emails for customer {customer_id}: {e}")
154
+
155
+ logger.info("βœ… 2 PM peak time email automation completed")
156
+
157
+ except Exception as e:
158
+ logger.error(f"❌ Error in 2 PM peak time email automation: {e}")
159
+
160
+ def _send_peak_time_emails_6pm(self):
161
+ """Send peak time emails at 6 PM based on user analysis"""
162
+ try:
163
+ logger.info("πŸ“§ Starting 6 PM peak time email automation based on user analysis...")
164
+
165
+ # Import email service
166
+ from email_automation_service import EmailAutomationService
167
+ email_service = EmailAutomationService()
168
+
169
+ # Send peak time emails using the enhanced method
170
+ email_service.send_peak_time_emails()
171
+
172
+ logger.info("βœ… 6 PM peak time email automation completed")
173
+
174
+ except Exception as e:
175
+ logger.error(f"❌ Error in 6 PM peak time email automation: {e}")
176
+
177
+ def _send_peak_time_emails_10pm(self):
178
+ """Send peak time emails at 10 PM based on user analysis"""
179
+ try:
180
+ logger.info("πŸ“§ Starting 10 PM peak time email automation based on user analysis...")
181
+
182
+ # Import email service
183
+ from email_automation_service import EmailAutomationService
184
+ email_service = EmailAutomationService()
185
+
186
+ # Send peak time emails using the enhanced method
187
+ email_service.send_peak_time_emails()
188
+
189
+ logger.info("βœ… 10 PM peak time email automation completed")
190
+
191
+ except Exception as e:
192
+ logger.error(f"❌ Error in 10 PM peak time email automation: {e}")
193
+
194
+ def _send_test_emails(self):
195
+ """Send test emails for debugging and testing"""
196
+ try:
197
+ logger.info("πŸ“§ Starting test email sending...")
198
+
199
+ # Import email service
200
+ from email_automation_service import EmailAutomationService
201
+ email_service = EmailAutomationService()
202
+
203
+ # Test with customer 144
204
+ test_customer_id = 144
205
+ test_recipient = "sameermujahid7777@gmail.com"
206
+
207
+ # Get customer analysis
208
+ analysis_data = self._get_customer_analysis(test_customer_id)
209
+ if analysis_data:
210
+ # Generate and send test email
211
+ email_result = email_service.generate_automated_email(
212
+ test_customer_id,
213
+ email_type="test_email"
214
+ )
215
+
216
+ if email_result.get('success'):
217
+ logger.info(f"βœ… Test email sent successfully to {test_recipient}")
218
+ else:
219
+ logger.warning(f"⚠️ Test email failed: {email_result.get('error')}")
220
+ else:
221
+ logger.warning("⚠️ No analysis data available for test email")
222
+
223
+ except Exception as e:
224
+ logger.error(f"❌ Error in test email sending: {e}")
225
+
226
+ def _get_all_customers(self):
227
+ """Get list of all customers from the system"""
228
+ try:
229
+ # This would typically come from your database
230
+ # For now, return a sample list of customer IDs
231
+ return [105, 106, 107, 108, 109] # Sample customer IDs
232
+ except Exception as e:
233
+ logger.error(f"❌ Error getting customers: {e}")
234
+ return []
235
+
236
+ def _get_customer_analysis(self, customer_id):
237
+ """Get customer analysis data"""
238
+ try:
239
+ # Import API service to get analysis
240
+ from api_service_enhanced import EnhancedLeadQualificationAPI
241
+ api_service = EnhancedLeadQualificationAPI()
242
+
243
+ # Get analysis data
244
+ analysis_data = api_service._get_analysis_data_parallel(customer_id)
245
+ return analysis_data
246
+
247
+ except Exception as e:
248
+ logger.error(f"❌ Error getting analysis for customer {customer_id}: {e}")
249
+ return None
250
+
251
+ def stop_background_services(self):
252
+ """Stop background services"""
253
+ logger.info("πŸ›‘ Stopping background services...")
254
+ self.is_running = False
255
+
256
+ if self.background_thread:
257
+ self.background_thread.join(timeout=5)
258
+
259
+ if self.email_thread:
260
+ self.email_thread.join(timeout=5)
261
+
262
+ logger.info("βœ… Background services stopped")
263
+
264
+ def main():
265
+ """Start the unified AI system"""
266
+ logger.info("=" * 80)
267
+ logger.info("πŸ€– Enhanced Unified AI Lead Analysis System Starting...")
268
+ logger.info("=" * 80)
269
+ logger.info(f"πŸ“… Start Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
270
+ logger.info("")
271
+
272
+ logger.info("πŸš€ Enhanced System Features:")
273
+ logger.info(" βœ… Single Port Solution (Port 7860)")
274
+ logger.info(" βœ… AI-Enhanced Lead Analysis")
275
+ logger.info(" βœ… Interactive Frontend Dashboard")
276
+ logger.info(" βœ… Email Automation & Testing")
277
+ logger.info(" βœ… Mock Data Support (No External Backend Required)")
278
+ logger.info(" βœ… Behavioral Analytics")
279
+ logger.info(" βœ… AI-Powered Recommendations")
280
+ logger.info(" βœ… 24-Hour Background Property Fetching")
281
+ logger.info(" βœ… Peak Time Email Automation (2 PM & 6 PM Daily)")
282
+ logger.info(" βœ… Small-Batch Parallel Processing")
283
+ logger.info(" βœ… ChromaDB Integration for Recommendations")
284
+ logger.info(" βœ… Email Testing & Automation Dashboard")
285
+ logger.info("")
286
+
287
+ logger.info("🌐 Access Points:")
288
+ logger.info(" πŸ“Š Main Dashboard: http://localhost:7860")
289
+ logger.info(" πŸ” Customer Analysis: http://localhost:7860/customer/105")
290
+ logger.info(" πŸ“‹ API Health: http://localhost:7860/api/health")
291
+ logger.info("")
292
+
293
+ logger.info("πŸ”„ Background Services:")
294
+ logger.info(" πŸ“₯ Property Fetching: Every 24 hours (small-batch parallel)")
295
+ logger.info(" πŸ“§ Peak Time Emails: Daily at 2:00 PM & 6:00 PM")
296
+ logger.info(" πŸ“§ Test Emails: Every hour for testing")
297
+ logger.info(" 🧠 AI Recommendations: Real-time processing")
298
+ logger.info("")
299
+
300
+ logger.info("πŸ§ͺ Test Instructions:")
301
+ logger.info(" 1. Open: http://localhost:7860")
302
+ logger.info(" 2. Enter Customer ID: 144")
303
+ logger.info(" 3. Click 'Analyze Customer'")
304
+ logger.info(" 4. Click 'AI Analysis' for enhanced insights")
305
+ logger.info(" 5. Test email features in the Email Testing Section:")
306
+ logger.info(" - Send Test Email")
307
+ logger.info(" - Test Email Triggers")
308
+ logger.info(" - Test Peak Time Emails")
309
+ logger.info(" - View Email Schedule")
310
+ logger.info(" - Check Email Logs")
311
+ logger.info("")
312
+
313
+ logger.info("πŸ“§ Email Features:")
314
+ logger.info(" - Test Email System")
315
+ logger.info(" - Test Automated Email")
316
+ logger.info(" - Peak Time Recommendations (2 PM & 6 PM Daily)")
317
+ logger.info(" - Email Automation Testing Dashboard")
318
+ logger.info(" - Email Triggers Analysis")
319
+ logger.info(" - Email Schedule Management")
320
+ logger.info(" - All emails sent to: shaiksameermujahid@gmail.com")
321
+ logger.info(" - Mock data ensures everything works without external services")
322
+ logger.info("")
323
+
324
+ logger.info("⏹️ Press Ctrl+C to stop the system")
325
+ logger.info("=" * 80)
326
+
327
+ # Initialize unified system
328
+ unified_system = UnifiedAISystem()
329
+ unified_system.is_running = True
330
+
331
+ try:
332
+ # Start background services
333
+ unified_system.start_background_services()
334
+
335
+ # Import and run the unified API service
336
+ from api_service_enhanced import EnhancedLeadQualificationAPI
337
+
338
+ api_service = EnhancedLeadQualificationAPI()
339
+ # Use port 7860 for Hugging Face Spaces
340
+ port = int(os.environ.get('PORT', 7860))
341
+ api_service.run(host='0.0.0.0', port=port, debug=False)
342
+
343
+ except KeyboardInterrupt:
344
+ logger.info("")
345
+ logger.info("πŸ›‘ Enhanced Unified AI System stopped by user")
346
+ unified_system.stop_background_services()
347
+ logger.info("βœ… System shutdown complete")
348
+ except Exception as e:
349
+ logger.error(f"❌ System error: {e}")
350
+ unified_system.stop_background_services()
351
+ sys.exit(1)
352
+
353
+ if __name__ == "__main__":
354
+ # Change to the lead directory
355
+ script_dir = os.path.dirname(os.path.abspath(__file__))
356
+ os.chdir(script_dir)
357
+
358
+ main()
email_automation_service.py ADDED
The diff for this file is too large to render. See raw diff
 
mock_data_service.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Mock Data Service
4
+ Provides sample customer data for testing when external backend is not available
5
+ """
6
+
7
+ import json
8
+ import random
9
+ from datetime import datetime, timedelta
10
+ from typing import List, Dict, Any
11
+
12
+ class MockDataService:
13
+ def __init__(self):
14
+ self.sample_properties = [
15
+ {
16
+ "propertyId": 1,
17
+ "propertyName": "Luxury Villa in Palm Jumeirah",
18
+ "propertyTypeName": "Villa",
19
+ "price": 15000000,
20
+ "viewCount": 15,
21
+ "totalDuration": 4500,
22
+ "lastViewedAt": "2024-01-15T10:30:00Z",
23
+ "location": "Palm Jumeirah",
24
+ "bedrooms": 5,
25
+ "bathrooms": 6,
26
+ "area": 8500,
27
+ "features": ["Private Pool", "Garden", "Gym", "Security"]
28
+ },
29
+ {
30
+ "propertyId": 2,
31
+ "propertyName": "Modern Apartment in Downtown",
32
+ "propertyTypeName": "Apartment",
33
+ "price": 3500000,
34
+ "viewCount": 8,
35
+ "totalDuration": 2800,
36
+ "lastViewedAt": "2024-01-14T14:20:00Z",
37
+ "location": "Downtown Dubai",
38
+ "bedrooms": 2,
39
+ "bathrooms": 2,
40
+ "area": 1200,
41
+ "features": ["Balcony", "Gym", "Pool", "Parking"]
42
+ },
43
+ {
44
+ "propertyId": 3,
45
+ "propertyName": "Beachfront Penthouse",
46
+ "propertyTypeName": "Penthouse",
47
+ "price": 25000000,
48
+ "viewCount": 12,
49
+ "totalDuration": 5200,
50
+ "lastViewedAt": "2024-01-13T16:45:00Z",
51
+ "location": "JBR",
52
+ "bedrooms": 4,
53
+ "bathrooms": 5,
54
+ "area": 3200,
55
+ "features": ["Beach Access", "Private Terrace", "Concierge", "Spa"]
56
+ },
57
+ {
58
+ "propertyId": 4,
59
+ "propertyName": "Family Villa in Emirates Hills",
60
+ "propertyTypeName": "Villa",
61
+ "price": 18000000,
62
+ "viewCount": 6,
63
+ "totalDuration": 3800,
64
+ "lastViewedAt": "2024-01-12T11:15:00Z",
65
+ "location": "Emirates Hills",
66
+ "bedrooms": 6,
67
+ "bathrooms": 7,
68
+ "area": 9500,
69
+ "features": ["Garden", "Pool", "Gym", "Staff Quarters"]
70
+ },
71
+ {
72
+ "propertyId": 5,
73
+ "propertyName": "Investment Apartment",
74
+ "propertyTypeName": "Apartment",
75
+ "price": 2200000,
76
+ "viewCount": 4,
77
+ "totalDuration": 1500,
78
+ "lastViewedAt": "2024-01-11T09:30:00Z",
79
+ "location": "Dubai Marina",
80
+ "bedrooms": 1,
81
+ "bathrooms": 1,
82
+ "area": 800,
83
+ "features": ["Marina View", "Gym", "Pool", "Rental Ready"]
84
+ },
85
+ {
86
+ "propertyId": 6,
87
+ "propertyName": "Luxury Townhouse",
88
+ "propertyTypeName": "Townhouse",
89
+ "price": 8500000,
90
+ "viewCount": 10,
91
+ "totalDuration": 4200,
92
+ "lastViewedAt": "2024-01-10T13:20:00Z",
93
+ "location": "Arabian Ranches",
94
+ "bedrooms": 4,
95
+ "bathrooms": 4,
96
+ "area": 2800,
97
+ "features": ["Garden", "Pool", "Golf Course", "Community"]
98
+ },
99
+ {
100
+ "propertyId": 7,
101
+ "propertyName": "Premium Office Space",
102
+ "propertyTypeName": "Office",
103
+ "price": 12000000,
104
+ "viewCount": 3,
105
+ "totalDuration": 1800,
106
+ "lastViewedAt": "2024-01-09T15:45:00Z",
107
+ "location": "DIFC",
108
+ "bedrooms": 0,
109
+ "bathrooms": 2,
110
+ "area": 5000,
111
+ "features": ["Premium Location", "Security", "Parking", "Meeting Rooms"]
112
+ },
113
+ {
114
+ "propertyId": 8,
115
+ "propertyName": "Retail Space in Mall",
116
+ "propertyTypeName": "Retail",
117
+ "price": 8000000,
118
+ "viewCount": 2,
119
+ "totalDuration": 1200,
120
+ "lastViewedAt": "2024-01-08T12:00:00Z",
121
+ "location": "Dubai Mall",
122
+ "bedrooms": 0,
123
+ "bathrooms": 1,
124
+ "area": 3000,
125
+ "features": ["High Foot Traffic", "Premium Location", "Storage", "Security"]
126
+ }
127
+ ]
128
+
129
+ def get_customer_data(self, customer_id: int) -> List[Dict[str, Any]]:
130
+ """Get mock customer data based on customer ID"""
131
+
132
+ # Generate different data based on customer ID
133
+ if customer_id == 105:
134
+ # High-value customer with luxury preferences
135
+ return self.sample_properties[:4] # First 4 properties (luxury)
136
+ elif customer_id == 106:
137
+ # Mid-range customer
138
+ return self.sample_properties[1:5] # Properties 2-5
139
+ elif customer_id == 107:
140
+ # Investment-focused customer
141
+ return [self.sample_properties[4], self.sample_properties[6], self.sample_properties[7]]
142
+ elif customer_id == 108:
143
+ # Budget-conscious customer
144
+ return [self.sample_properties[1], self.sample_properties[4]]
145
+ else:
146
+ # Random selection for other customer IDs
147
+ num_properties = random.randint(2, 6)
148
+ return random.sample(self.sample_properties, num_properties)
149
+
150
+ def get_customer_profile(self, customer_id: int) -> Dict[str, Any]:
151
+ """Get customer profile information"""
152
+ profiles = {
153
+ 105: {
154
+ "customerName": "Ahmed Al Mansouri",
155
+ "email": "ahmed.mansouri@email.com",
156
+ "phone": "+971501234567",
157
+ "preferredLocation": "Palm Jumeirah",
158
+ "budgetRange": "15M-25M",
159
+ "propertyType": "Villa",
160
+ "leadSource": "Website",
161
+ "lastContact": "2024-01-15T10:30:00Z"
162
+ },
163
+ 106: {
164
+ "customerName": "Sarah Johnson",
165
+ "email": "sarah.johnson@email.com",
166
+ "phone": "+971502345678",
167
+ "preferredLocation": "Downtown Dubai",
168
+ "budgetRange": "3M-8M",
169
+ "propertyType": "Apartment",
170
+ "leadSource": "Referral",
171
+ "lastContact": "2024-01-14T14:20:00Z"
172
+ },
173
+ 107: {
174
+ "customerName": "Mohammed Rahman",
175
+ "email": "m.rahman@email.com",
176
+ "phone": "+971503456789",
177
+ "preferredLocation": "Dubai Marina",
178
+ "budgetRange": "2M-15M",
179
+ "propertyType": "Mixed",
180
+ "leadSource": "Investment Portal",
181
+ "lastContact": "2024-01-13T16:45:00Z"
182
+ },
183
+ 108: {
184
+ "customerName": "Fatima Hassan",
185
+ "email": "fatima.hassan@email.com",
186
+ "phone": "+971504567890",
187
+ "preferredLocation": "Dubai Marina",
188
+ "budgetRange": "2M-4M",
189
+ "propertyType": "Apartment",
190
+ "leadSource": "Social Media",
191
+ "lastContact": "2024-01-12T11:15:00Z"
192
+ }
193
+ }
194
+
195
+ return profiles.get(customer_id, {
196
+ "customerName": f"Customer {customer_id}",
197
+ "email": f"customer{customer_id}@email.com",
198
+ "phone": f"+97150{random.randint(1000000, 9999999)}",
199
+ "preferredLocation": random.choice(["Dubai Marina", "Downtown Dubai", "Palm Jumeirah", "JBR"]),
200
+ "budgetRange": random.choice(["2M-5M", "5M-10M", "10M-20M", "20M+"]),
201
+ "propertyType": random.choice(["Apartment", "Villa", "Townhouse", "Mixed"]),
202
+ "leadSource": random.choice(["Website", "Referral", "Social Media", "Advertisement"]),
203
+ "lastContact": datetime.now().isoformat()
204
+ })
205
+
206
+ def generate_engagement_metrics(self, customer_id: int) -> Dict[str, Any]:
207
+ """Generate engagement metrics for the customer"""
208
+ base_engagement = random.randint(30, 90)
209
+
210
+ # Adjust based on customer ID
211
+ if customer_id == 105:
212
+ base_engagement = 85 # High engagement
213
+ elif customer_id == 106:
214
+ base_engagement = 65 # Medium engagement
215
+ elif customer_id == 107:
216
+ base_engagement = 45 # Lower engagement
217
+ elif customer_id == 108:
218
+ base_engagement = 55 # Medium-low engagement
219
+
220
+ return {
221
+ "totalViews": random.randint(5, 25),
222
+ "totalDuration": random.randint(1800, 7200), # 30 minutes to 2 hours
223
+ "engagementScore": base_engagement,
224
+ "lastActivity": datetime.now().isoformat(),
225
+ "favoriteProperties": random.randint(1, 4),
226
+ "searchQueries": random.randint(3, 12),
227
+ "emailOpens": random.randint(1, 8),
228
+ "websiteVisits": random.randint(2, 15)
229
+ }
230
+
231
+ # Global instance
232
+ mock_data_service = MockDataService()
requirements.txt ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Flask
2
+ Flask-CORS
3
+ Werkzeug
4
+ requests
5
+ urllib3
6
+ pandas
7
+ numpy
8
+ scikit-learn
9
+ torch
10
+ torchvision
11
+ torchaudio
12
+ transformers
13
+ sentence-transformers
14
+ chromadb
15
+ faiss-cpu
16
+ python-dotenv
17
+ colorama
18
+ coloredlogs
19
+ schedule
templates/ai_lead_analysis.html ADDED
@@ -0,0 +1,1714 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>πŸ€– AI Lead Analysis Dashboard</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
9
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
10
+ <style>
11
+ :root {
12
+ --primary-color: #2563eb;
13
+ --secondary-color: #7c3aed;
14
+ --accent-color: #059669;
15
+ --success-color: #10b981;
16
+ --warning-color: #f59e0b;
17
+ --danger-color: #ef4444;
18
+ --light-color: #f8fafc;
19
+ --dark-color: #1e293b;
20
+ --info-color: #0ea5e9;
21
+ }
22
+
23
+ * {
24
+ margin: 0;
25
+ padding: 0;
26
+ box-sizing: border-box;
27
+ }
28
+
29
+ body {
30
+ font-family: 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
31
+ background: #f1f5f9;
32
+ color: var(--dark-color);
33
+ line-height: 1.6;
34
+ min-height: 100vh;
35
+ }
36
+
37
+ .main-container {
38
+ max-width: 1400px;
39
+ margin: 0 auto;
40
+ padding: 20px;
41
+ }
42
+
43
+ /* Header Styles */
44
+ .hero-header {
45
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
46
+ color: white;
47
+ border-radius: 20px;
48
+ padding: 40px;
49
+ margin-bottom: 30px;
50
+ text-align: center;
51
+ box-shadow: 0 20px 40px rgba(37, 99, 235, 0.2);
52
+ }
53
+
54
+ .hero-header h1 {
55
+ font-size: 3rem;
56
+ font-weight: 700;
57
+ margin-bottom: 15px;
58
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
59
+ }
60
+
61
+ .hero-header p {
62
+ font-size: 1.3rem;
63
+ margin-bottom: 25px;
64
+ opacity: 0.95;
65
+ }
66
+
67
+ .ai-badge {
68
+ background: rgba(255, 255, 255, 0.2);
69
+ color: white;
70
+ padding: 12px 25px;
71
+ border-radius: 50px;
72
+ font-size: 1.1rem;
73
+ font-weight: 600;
74
+ display: inline-block;
75
+ backdrop-filter: blur(10px);
76
+ border: 1px solid rgba(255, 255, 255, 0.3);
77
+ }
78
+
79
+ /* Card Styles */
80
+ .card {
81
+ background: white;
82
+ border-radius: 16px;
83
+ padding: 30px;
84
+ margin-bottom: 25px;
85
+ box-shadow: 0 4px 25px rgba(0, 0, 0, 0.08);
86
+ border: 1px solid #e2e8f0;
87
+ transition: all 0.3s ease;
88
+ }
89
+
90
+ .card:hover {
91
+ transform: translateY(-2px);
92
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
93
+ }
94
+
95
+ .card h3 {
96
+ color: var(--dark-color);
97
+ font-weight: 600;
98
+ margin-bottom: 20px;
99
+ font-size: 1.5rem;
100
+ display: flex;
101
+ align-items: center;
102
+ gap: 10px;
103
+ }
104
+
105
+ .card h4 {
106
+ color: var(--primary-color);
107
+ font-weight: 600;
108
+ margin-bottom: 15px;
109
+ font-size: 1.2rem;
110
+ }
111
+
112
+ /* Form Styles */
113
+ .form-control {
114
+ border: 2px solid #e2e8f0;
115
+ border-radius: 12px;
116
+ padding: 15px;
117
+ font-size: 1rem;
118
+ transition: all 0.3s ease;
119
+ background: white;
120
+ color: var(--dark-color);
121
+ }
122
+
123
+ .form-control:focus {
124
+ border-color: var(--primary-color);
125
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
126
+ outline: none;
127
+ }
128
+
129
+ .form-label {
130
+ color: var(--dark-color);
131
+ font-weight: 600;
132
+ margin-bottom: 8px;
133
+ font-size: 1rem;
134
+ }
135
+
136
+ /* Button Styles */
137
+ .btn {
138
+ border-radius: 12px;
139
+ padding: 12px 25px;
140
+ font-weight: 600;
141
+ font-size: 1rem;
142
+ transition: all 0.3s ease;
143
+ border: none;
144
+ cursor: pointer;
145
+ display: inline-flex;
146
+ align-items: center;
147
+ gap: 8px;
148
+ }
149
+
150
+ .btn-primary {
151
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
152
+ color: white;
153
+ }
154
+
155
+ .btn-primary:hover {
156
+ transform: translateY(-2px);
157
+ box-shadow: 0 8px 20px rgba(37, 99, 235, 0.3);
158
+ }
159
+
160
+ .btn-success {
161
+ background: var(--success-color);
162
+ color: white;
163
+ }
164
+
165
+ .btn-success:hover {
166
+ background: #059669;
167
+ transform: translateY(-2px);
168
+ }
169
+
170
+ .btn-warning {
171
+ background: var(--warning-color);
172
+ color: white;
173
+ }
174
+
175
+ .btn-info {
176
+ background: var(--info-color);
177
+ color: white;
178
+ }
179
+
180
+ .btn-danger {
181
+ background: var(--danger-color);
182
+ color: white;
183
+ }
184
+
185
+ .btn-outline-purple {
186
+ background: transparent;
187
+ border: 2px solid var(--secondary-color);
188
+ color: var(--secondary-color);
189
+ }
190
+
191
+ .btn-outline-purple:hover {
192
+ background: var(--secondary-color);
193
+ border-color: var(--secondary-color);
194
+ color: white;
195
+ transform: translateY(-2px);
196
+ }
197
+
198
+ .btn-secondary {
199
+ background: #6c757d;
200
+ color: white;
201
+ }
202
+
203
+ .btn-outline-success {
204
+ background: transparent;
205
+ border: 2px solid var(--success-color);
206
+ color: var(--success-color);
207
+ }
208
+
209
+ .btn-outline-success:hover {
210
+ background: var(--success-color);
211
+ color: white;
212
+ transform: translateY(-2px);
213
+ }
214
+
215
+ .btn-outline-info {
216
+ background: transparent;
217
+ border: 2px solid var(--info-color);
218
+ color: var(--info-color);
219
+ }
220
+
221
+ .btn-outline-info:hover {
222
+ background: var(--info-color);
223
+ color: white;
224
+ transform: translateY(-2px);
225
+ }
226
+
227
+ .btn-outline-warning {
228
+ background: transparent;
229
+ border: 2px solid var(--warning-color);
230
+ color: var(--warning-color);
231
+ }
232
+
233
+ .btn-outline-warning:hover {
234
+ background: var(--warning-color);
235
+ color: white;
236
+ transform: translateY(-2px);
237
+ }
238
+
239
+ /* Metric Cards */
240
+ .metric-card {
241
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
242
+ color: white;
243
+ border-radius: 16px;
244
+ padding: 25px;
245
+ text-align: center;
246
+ transition: transform 0.3s ease;
247
+ }
248
+
249
+ .metric-card:hover {
250
+ transform: translateY(-3px);
251
+ }
252
+
253
+ .metric-value {
254
+ font-size: 2.5rem;
255
+ font-weight: 700;
256
+ margin-bottom: 8px;
257
+ }
258
+
259
+ .metric-label {
260
+ font-size: 1rem;
261
+ opacity: 0.9;
262
+ font-weight: 500;
263
+ }
264
+
265
+ /* Alert Styles */
266
+ .alert {
267
+ border: none;
268
+ border-radius: 12px;
269
+ padding: 20px;
270
+ margin-bottom: 20px;
271
+ font-weight: 500;
272
+ }
273
+
274
+ .alert-success {
275
+ background: linear-gradient(135deg, #d1fae5, #a7f3d0);
276
+ color: #065f46;
277
+ border-left: 4px solid var(--success-color);
278
+ }
279
+
280
+ .alert-info {
281
+ background: linear-gradient(135deg, #dbeafe, #bfdbfe);
282
+ color: #1e40af;
283
+ border-left: 4px solid var(--info-color);
284
+ }
285
+
286
+ .alert-warning {
287
+ background: linear-gradient(135deg, #fef3c7, #fde68a);
288
+ color: #92400e;
289
+ border-left: 4px solid var(--warning-color);
290
+ }
291
+
292
+ .alert-danger {
293
+ background: linear-gradient(135deg, #fee2e2, #fca5a5);
294
+ color: #991b1b;
295
+ border-left: 4px solid var(--danger-color);
296
+ }
297
+
298
+ /* Email Section Specific Styles */
299
+ .email-section {
300
+ background: white;
301
+ border: 2px solid #e2e8f0;
302
+ border-radius: 16px;
303
+ padding: 30px;
304
+ margin-bottom: 30px;
305
+ }
306
+
307
+ .email-section h3 {
308
+ color: var(--dark-color);
309
+ margin-bottom: 25px;
310
+ font-weight: 600;
311
+ }
312
+
313
+ .email-section .form-control {
314
+ background: #f8fafc;
315
+ border: 2px solid #e2e8f0;
316
+ color: var(--dark-color);
317
+ }
318
+
319
+ .email-section .form-control::placeholder {
320
+ color: #64748b;
321
+ }
322
+
323
+ .email-section .form-control:focus {
324
+ background: white;
325
+ border-color: var(--primary-color);
326
+ }
327
+
328
+ /* Timeline Styles */
329
+ .timeline {
330
+ background: #f8fafc;
331
+ border-radius: 12px;
332
+ padding: 20px;
333
+ }
334
+
335
+ .timeline-item {
336
+ background: white;
337
+ border: 1px solid #e2e8f0;
338
+ border-radius: 8px;
339
+ padding: 15px;
340
+ margin-bottom: 10px;
341
+ color: var(--dark-color);
342
+ font-weight: 500;
343
+ }
344
+
345
+ /* Property Cards */
346
+ .property-item {
347
+ background: white;
348
+ border: 1px solid #e2e8f0;
349
+ border-radius: 12px;
350
+ padding: 20px;
351
+ transition: all 0.3s ease;
352
+ }
353
+
354
+ .property-item:hover {
355
+ transform: translateY(-2px);
356
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
357
+ border-color: var(--primary-color);
358
+ }
359
+
360
+ .property-name {
361
+ font-weight: 600;
362
+ color: var(--primary-color);
363
+ margin-bottom: 10px;
364
+ font-size: 1.1rem;
365
+ }
366
+
367
+ .property-details {
368
+ color: var(--dark-color);
369
+ font-size: 0.95rem;
370
+ }
371
+
372
+ .property-details span {
373
+ font-weight: 600;
374
+ color: var(--secondary-color);
375
+ }
376
+
377
+ /* Multi-AI Feature Box */
378
+ .multi-ai-box {
379
+ background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
380
+ border: 2px solid #0ea5e9;
381
+ border-radius: 16px;
382
+ padding: 25px;
383
+ margin: 20px 0;
384
+ }
385
+
386
+ .multi-ai-box h6 {
387
+ color: var(--info-color);
388
+ font-weight: 600;
389
+ margin-bottom: 15px;
390
+ }
391
+
392
+ .multi-ai-box ul {
393
+ margin: 0;
394
+ padding-left: 20px;
395
+ }
396
+
397
+ .multi-ai-box li {
398
+ color: var(--dark-color);
399
+ margin-bottom: 5px;
400
+ font-weight: 500;
401
+ }
402
+
403
+ /* Loading Animation */
404
+ .loading {
405
+ text-align: center;
406
+ padding: 60px;
407
+ background: white;
408
+ border-radius: 16px;
409
+ margin: 20px 0;
410
+ }
411
+
412
+ .loading i {
413
+ font-size: 3rem;
414
+ color: var(--primary-color);
415
+ animation: spin 1s linear infinite;
416
+ }
417
+
418
+ .loading h3 {
419
+ color: var(--dark-color);
420
+ margin: 20px 0 10px 0;
421
+ }
422
+
423
+ .loading p {
424
+ color: #64748b;
425
+ }
426
+
427
+ @keyframes spin {
428
+ 0% { transform: rotate(0deg); }
429
+ 100% { transform: rotate(360deg); }
430
+ }
431
+
432
+ /* Hidden class */
433
+ .hidden {
434
+ display: none;
435
+ }
436
+
437
+ /* Search Form */
438
+ .search-section {
439
+ background: white;
440
+ border-radius: 16px;
441
+ padding: 30px;
442
+ margin-bottom: 30px;
443
+ border: 2px solid #e2e8f0;
444
+ }
445
+
446
+ .search-form {
447
+ display: flex;
448
+ gap: 20px;
449
+ align-items: end;
450
+ justify-content: center;
451
+ flex-wrap: wrap;
452
+ }
453
+
454
+ /* Responsive Design */
455
+ @media (max-width: 768px) {
456
+ .hero-header h1 {
457
+ font-size: 2rem;
458
+ }
459
+
460
+ .hero-header p {
461
+ font-size: 1.1rem;
462
+ }
463
+
464
+ .search-form {
465
+ flex-direction: column;
466
+ align-items: stretch;
467
+ }
468
+
469
+ .metric-value {
470
+ font-size: 2rem;
471
+ }
472
+ }
473
+
474
+ /* Status Badges */
475
+ .status-badge {
476
+ padding: 8px 16px;
477
+ border-radius: 50px;
478
+ font-weight: 600;
479
+ font-size: 0.9rem;
480
+ text-transform: uppercase;
481
+ letter-spacing: 0.5px;
482
+ }
483
+
484
+ .status-hot {
485
+ background: linear-gradient(135deg, #fee2e2, #fca5a5);
486
+ color: #991b1b;
487
+ }
488
+
489
+ .status-warm {
490
+ background: linear-gradient(135deg, #fef3c7, #fde68a);
491
+ color: #92400e;
492
+ }
493
+
494
+ .status-cold {
495
+ background: linear-gradient(135deg, #f1f5f9, #cbd5e1);
496
+ color: #475569;
497
+ }
498
+
499
+ /* Progress Bars */
500
+ .progress {
501
+ height: 12px;
502
+ border-radius: 6px;
503
+ background-color: #e2e8f0;
504
+ overflow: hidden;
505
+ }
506
+
507
+ .progress-bar {
508
+ background: linear-gradient(90deg, var(--success-color), var(--accent-color));
509
+ border-radius: 6px;
510
+ transition: width 0.6s ease;
511
+ }
512
+ </style>
513
+ </head>
514
+ <body>
515
+ <div class="main-container">
516
+ <!-- Hero Header -->
517
+ <div class="hero-header">
518
+ <h1><i class="fas fa-robot"></i> AI Lead Analysis Dashboard</h1>
519
+ <p>Advanced Customer Behavior Analytics with Multi-AI Recommendations</p>
520
+ <div class="ai-badge">
521
+ <i class="fas fa-brain"></i> Powered by Multiple AI Models
522
+ </div>
523
+ </div>
524
+
525
+ <!-- Search Section -->
526
+ <div class="search-section">
527
+ <h3><i class="fas fa-search"></i> Customer Analysis</h3>
528
+ <div class="search-form">
529
+ <div class="form-group">
530
+ <label for="customerId" class="form-label">Customer ID:</label>
531
+ <input type="number" id="customerId" class="form-control" value="144" min="1" placeholder="Enter Customer ID" style="width: 200px;">
532
+ </div>
533
+ <div class="form-group">
534
+ <button type="button" class="btn btn-primary" onclick="analyzeCustomer()">
535
+ <i class="fas fa-chart-line"></i> Analyze Customer
536
+ </button>
537
+ </div>
538
+ </div>
539
+ </div>
540
+
541
+ <!-- Loading Section -->
542
+ <div id="loadingSection" class="loading hidden">
543
+ <i class="fas fa-spinner"></i>
544
+ <h3>Analyzing Customer Data...</h3>
545
+ <p>Please wait while we process the information with AI models</p>
546
+ </div>
547
+
548
+ <!-- Analysis Results -->
549
+ <div id="analysisResults" class="hidden">
550
+ <!-- Lead Status Card -->
551
+ <div class="card">
552
+ <h3><i class="fas fa-user-check"></i> Lead Qualification Status</h3>
553
+ <div class="row">
554
+ <div class="col-md-8">
555
+ <div id="leadStatus"></div>
556
+ </div>
557
+ <div class="col-md-4">
558
+ <div class="metric-card">
559
+ <div class="metric-value" id="leadScore">0</div>
560
+ <div class="metric-label">Lead Score</div>
561
+ </div>
562
+ </div>
563
+ </div>
564
+ <div id="leadFactors" class="mt-4"></div>
565
+ <div id="conversionDetails" class="mt-4"></div>
566
+ </div>
567
+
568
+ <!-- Key Metrics -->
569
+ <div class="row mb-4">
570
+ <div class="col-md-3">
571
+ <div class="metric-card">
572
+ <div class="metric-value" id="totalViews">0</div>
573
+ <div class="metric-label">Total Views</div>
574
+ </div>
575
+ </div>
576
+ <div class="col-md-3">
577
+ <div class="metric-card">
578
+ <div class="metric-value" id="totalDuration">0h 0m</div>
579
+ <div class="metric-label">Total Duration</div>
580
+ </div>
581
+ </div>
582
+ <div class="col-md-3">
583
+ <div class="metric-card">
584
+ <div class="metric-value" id="engagementScore">0</div>
585
+ <div class="metric-label">Engagement Score</div>
586
+ </div>
587
+ </div>
588
+ <div class="col-md-3">
589
+ <div class="metric-card">
590
+ <div class="metric-value" id="conversionProbability">0%</div>
591
+ <div class="metric-label">Conversion Probability</div>
592
+ </div>
593
+ </div>
594
+ </div>
595
+
596
+ <!-- Property Preferences -->
597
+ <div class="card">
598
+ <h3><i class="fas fa-home"></i> Property Preferences & Analysis</h3>
599
+ <div class="row">
600
+ <div class="col-md-4">
601
+ <h4><i class="fas fa-tags"></i> Preferred Types</h4>
602
+ <div id="propertyPreferences"></div>
603
+ </div>
604
+ <div class="col-md-4">
605
+ <h4><i class="fas fa-clock"></i> Viewing Patterns</h4>
606
+ <div id="viewingPatterns"></div>
607
+ </div>
608
+ <div class="col-md-4">
609
+ <h4><i class="fas fa-chart-line"></i> Activity Timeline</h4>
610
+ <div id="activityTimeline"></div>
611
+ </div>
612
+ </div>
613
+ </div>
614
+
615
+ <!-- Price Analysis -->
616
+ <div class="card">
617
+ <h3><i class="fas fa-money-bill-wave"></i> Price Analysis</h3>
618
+ <div id="priceAnalysis"></div>
619
+ </div>
620
+
621
+ <!-- Multi-AI Recommendations System -->
622
+ <div class="card">
623
+ <h3><i class="fas fa-brain"></i> Multi-AI Recommendations Engine</h3>
624
+ <div class="row">
625
+ <div class="col-md-6">
626
+ <div class="form-group mb-3">
627
+ <label for="multiAiEmail" class="form-label">Email Address for Multi-AI Recommendations:</label>
628
+ <input type="email" id="multiAiEmail" class="form-control" value="shaiksameermujahid@gmail.com">
629
+ </div>
630
+ <div class="form-group mb-3">
631
+ <label for="emailCount" class="form-label">Number of AI Emails to Send:</label>
632
+ <select id="emailCount" class="form-control">
633
+ <option value="5">5 Emails</option>
634
+ <option value="10" selected>10 Emails</option>
635
+ <option value="15">15 Emails</option>
636
+ </select>
637
+ </div>
638
+ </div>
639
+ <div class="col-md-6">
640
+ <div class="multi-ai-box">
641
+ <h6><i class="fas fa-info-circle"></i> Multi-AI Features:</h6>
642
+ <ul>
643
+ <li>🏑 Property-based recommendations</li>
644
+ <li>πŸ’° Price-based matching</li>
645
+ <li>πŸ“ Location-based suggestions</li>
646
+ <li>πŸ” Similarity-based findings</li>
647
+ <li>🧠 Behavioral analysis recommendations</li>
648
+ <li>⭐ Premium properties collection</li>
649
+ <li>πŸ’΅ Budget-friendly options</li>
650
+ <li>πŸ“ˆ Trending properties</li>
651
+ <li>πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦ Family-oriented properties</li>
652
+ <li>πŸ’Ό Investment opportunities</li>
653
+ </ul>
654
+ </div>
655
+ </div>
656
+ </div>
657
+ <div class="row mt-3">
658
+ <div class="col-12">
659
+ <button type="button" class="btn btn-primary btn-lg me-3" onclick="sendMultiAIRecommendations()">
660
+ <i class="fas fa-robot"></i> Send Multi-AI Recommendations
661
+ </button>
662
+ <button type="button" class="btn btn-success me-3" onclick="getChromaDBRecommendations()">
663
+ <i class="fas fa-database"></i> Get ChromaDB Recommendations
664
+ </button>
665
+ <button type="button" class="btn btn-info" onclick="fetchAllProperties()">
666
+ <i class="fas fa-download"></i> Fetch All Properties to ChromaDB
667
+ </button>
668
+ </div>
669
+ </div>
670
+
671
+ <!-- Multi-AI Results -->
672
+ <div id="multiAiResults" class="mt-4"></div>
673
+
674
+ <!-- ChromaDB Recommendations Results -->
675
+ <div id="chromaDbResults" class="mt-4"></div>
676
+ </div>
677
+
678
+ <!-- Email Testing Section -->
679
+ <div class="card">
680
+ <h3><i class="fas fa-envelope"></i> Comprehensive Email Testing & Analysis Suite</h3>
681
+ <div class="row">
682
+ <div class="col-md-6">
683
+ <div class="form-group mb-3">
684
+ <label for="testEmail" class="form-label">Test Email Address:</label>
685
+ <input type="email" id="testEmail" class="form-control" value="shaiksameermujahid@gmail.com">
686
+ <small class="text-muted">All emails will be sent to this address</small>
687
+ </div>
688
+
689
+ <!-- Main Email Tests -->
690
+ <h6><i class="fas fa-envelope"></i> Core Email Functions:</h6>
691
+ <div class="d-flex gap-2 flex-wrap mb-3">
692
+ <button type="button" class="btn btn-success btn-sm" onclick="testSendGridConnection()">
693
+ <i class="fas fa-wifi"></i> Test SendGrid
694
+ </button>
695
+ <button type="button" class="btn btn-warning btn-sm" onclick="previewAllEmailContent()">
696
+ <i class="fas fa-eye"></i> Preview 10 Emails
697
+ </button>
698
+ </div>
699
+
700
+ <!-- Analysis & Status -->
701
+ <h6><i class="fas fa-chart-line"></i> Analysis & Status:</h6>
702
+ <div class="d-flex gap-2 flex-wrap mb-3">
703
+ <button type="button" class="btn btn-info btn-sm" onclick="getEmailAnalysisBasis()">
704
+ <i class="fas fa-search-plus"></i> Show Analysis Basis
705
+ </button>
706
+ <button type="button" class="btn btn-secondary btn-sm" onclick="getEmailStatus()">
707
+ <i class="fas fa-chart-bar"></i> Email Status
708
+ </button>
709
+ </div>
710
+ </div>
711
+ <div class="col-md-6">
712
+ <!-- Main Testing Actions -->
713
+ <h6><i class="fas fa-rocket"></i> Main Email Actions:</h6>
714
+ <div class="d-flex gap-2 flex-wrap mb-2">
715
+ <button type="button" class="btn btn-primary" onclick="testAll10Emails()">
716
+ <i class="fas fa-envelope-open-text"></i> Send All 10 AI Emails
717
+ </button>
718
+ </div>
719
+ <div class="alert alert-info mb-3">
720
+ <small><strong>πŸ“§ Multi-AI Email System:</strong> Each email uses a different AI model to retrieve personalized properties from ChromaDB based on your analysis.</small>
721
+ </div>
722
+
723
+ <!-- Property Management -->
724
+ <h6><i class="fas fa-database"></i> Property Management:</h6>
725
+ <div class="d-flex gap-2 flex-wrap mb-2">
726
+ <button type="button" class="btn btn-info btn-sm" onclick="checkChromaDBStatus()">
727
+ <i class="fas fa-heartbeat"></i> Check ChromaDB Status
728
+ </button>
729
+ <button type="button" class="btn btn-success btn-sm" onclick="fetchAllProperties()">
730
+ <i class="fas fa-download"></i> Fetch Properties
731
+ </button>
732
+ </div>
733
+ <div class="d-flex gap-2 flex-wrap mb-4">
734
+ <button type="button" class="btn btn-secondary btn-sm" onclick="getChromaDBRecommendations()">
735
+ <i class="fas fa-search"></i> Test Recommendations
736
+ </button>
737
+ </div>
738
+
739
+ <!-- Saved Emails Management -->
740
+ <h6><i class="fas fa-folder-open"></i> Saved Emails:</h6>
741
+ <div class="d-flex gap-2 flex-wrap mb-3">
742
+ <button type="button" class="btn btn-warning btn-sm" onclick="listSavedEmails()">
743
+ <i class="fas fa-folder-open"></i> View Saved Emails
744
+ </button>
745
+ </div>
746
+
747
+ <div class="alert alert-warning mb-3">
748
+ <small><strong>πŸ“ SendGrid Limit Solution:</strong> When SendGrid limits are exceeded, emails are automatically saved to your local <code>./saved_emails/</code> folder. Click "View Saved Emails" to see and open them.</small>
749
+ </div>
750
+
751
+ <h6>Email Configuration:</h6>
752
+ <div class="timeline">
753
+ <div class="timeline-item">
754
+ <strong>From:</strong> shaiksameermujahid@gmail.com
755
+ </div>
756
+ <div class="timeline-item">
757
+ <strong>To:</strong> sameermujahid7777@gmail.com
758
+ </div>
759
+ <div class="timeline-item">
760
+ <strong>2:00 PM Daily</strong> - Peak time recommendations
761
+ </div>
762
+ <div class="timeline-item">
763
+ <strong>6:00 PM Daily</strong> - Evening opportunities
764
+ </div>
765
+ <div class="timeline-item">
766
+ <strong>Every Hour</strong> - System test emails
767
+ </div>
768
+ </div>
769
+ </div>
770
+ </div>
771
+ </div>
772
+
773
+ <!-- Email Dashboard -->
774
+ <div class="card">
775
+ <h3><i class="fas fa-tachometer-alt"></i> Email Testing Dashboard</h3>
776
+ <div class="row mb-3">
777
+ <div class="col-md-3">
778
+ <div class="metric-card">
779
+ <div class="metric-value" id="totalEmails">0</div>
780
+ <div class="metric-label">Total Emails</div>
781
+ </div>
782
+ </div>
783
+ <div class="col-md-3">
784
+ <div class="metric-card">
785
+ <div class="metric-value" id="successEmails">0</div>
786
+ <div class="metric-label">Successful</div>
787
+ </div>
788
+ </div>
789
+ <div class="col-md-3">
790
+ <div class="metric-card">
791
+ <div class="metric-value" id="failedEmails">0</div>
792
+ <div class="metric-label">Failed</div>
793
+ </div>
794
+ </div>
795
+ <div class="col-md-3">
796
+ <div class="metric-card">
797
+ <div class="metric-value" id="scheduledEmails">0</div>
798
+ <div class="metric-label">Scheduled</div>
799
+ </div>
800
+ </div>
801
+ </div>
802
+ <div class="d-flex gap-2 flex-wrap">
803
+ <button type="button" class="btn btn-success" onclick="testAllEmailTypes()">
804
+ <i class="fas fa-play"></i> Test All Email Types
805
+ </button>
806
+ <button type="button" class="btn btn-warning" onclick="clearEmailLogs()">
807
+ <i class="fas fa-trash"></i> Clear Logs
808
+ </button>
809
+ <button type="button" class="btn btn-primary" onclick="sendAllIntendedEmails()">
810
+ <i class="fas fa-paper-plane"></i> Send All Intended Emails
811
+ </button>
812
+ <button type="button" class="btn btn-danger" onclick="sendMultiAIEmails()">
813
+ <i class="fas fa-robot"></i> Send 10 AI Emails
814
+ </button>
815
+ </div>
816
+ </div>
817
+
818
+ <!-- Email Logs -->
819
+ <div class="card">
820
+ <h3><i class="fas fa-list"></i> Email Activity Logs</h3>
821
+ <div id="emailLogs"></div>
822
+ </div>
823
+
824
+ <!-- Properties Viewed -->
825
+ <div class="card">
826
+ <h3><i class="fas fa-eye"></i> Properties Viewed - Detailed Analysis</h3>
827
+ <div class="row" id="propertiesViewed"></div>
828
+ </div>
829
+
830
+ <!-- AI Recommendations -->
831
+ <div class="card">
832
+ <h3><i class="fas fa-robot"></i> AI-Powered Property Recommendations</h3>
833
+ <div class="alert alert-info">
834
+ <h6><i class="fas fa-lightbulb"></i> Personalized Recommendations</h6>
835
+ <div id="aiRecommendations"></div>
836
+ </div>
837
+ <div id="propertyRecommendations"></div>
838
+ </div>
839
+ </div>
840
+ </div>
841
+
842
+ <script>
843
+ let currentCustomerId = 144;
844
+ let analysisData = null;
845
+
846
+ async function analyzeCustomer() {
847
+ const customerId = document.getElementById('customerId').value;
848
+ if (!customerId) {
849
+ showError('Please enter a Customer ID');
850
+ return;
851
+ }
852
+
853
+ currentCustomerId = customerId;
854
+
855
+ // Show loading
856
+ document.getElementById('loadingSection').classList.remove('hidden');
857
+ document.getElementById('analysisResults').classList.add('hidden');
858
+
859
+ try {
860
+ const response = await fetch(`/api/lead-analysis/${customerId}`);
861
+ const data = await response.json();
862
+
863
+ if (data.success) {
864
+ analysisData = data.data;
865
+ displayAnalysis(data.data);
866
+ loadEmailLogs();
867
+ } else {
868
+ showError('Analysis failed: ' + (data.error || 'Unknown error'));
869
+ }
870
+ } catch (error) {
871
+ showError('Error analyzing customer: ' + error.message);
872
+ } finally {
873
+ document.getElementById('loadingSection').classList.add('hidden');
874
+ document.getElementById('analysisResults').classList.remove('hidden');
875
+ }
876
+ }
877
+
878
+ function displayAnalysis(data) {
879
+ // Lead Status
880
+ const leadQual = data.lead_qualification;
881
+ document.getElementById('leadStatus').innerHTML = `
882
+ <div class="status-badge status-${leadQual.lead_status.toLowerCase()}">
883
+ ${leadQual.lead_status}
884
+ </div>
885
+ <p class="mt-3">${leadQual.status_description}</p>
886
+ <div class="progress mt-3">
887
+ <div class="progress-bar" style="width: ${leadQual.lead_score}%"></div>
888
+ </div>
889
+ <small class="text-muted mt-2">${leadQual.lead_score}/${leadQual.max_possible_score} points</small>
890
+ `;
891
+
892
+ document.getElementById('leadScore').textContent = leadQual.lead_score;
893
+
894
+ // Key Metrics
895
+ const summary = data.summary;
896
+ document.getElementById('totalViews').textContent = summary.total_views;
897
+ document.getElementById('totalDuration').textContent = formatDuration(summary.total_duration);
898
+ document.getElementById('engagementScore').textContent = Math.round(summary.engagement_score);
899
+
900
+ const conversionProb = data.analytics.conversion_probability;
901
+ document.getElementById('conversionProbability').textContent = Math.round(conversionProb.final_probability) + '%';
902
+
903
+ // Property Preferences
904
+ const analytics = data.analytics;
905
+ document.getElementById('propertyPreferences').innerHTML = `
906
+ <ul class="list-unstyled">
907
+ ${analytics.preferred_property_types.map(type => `<li><i class="fas fa-check-circle text-success"></i> ${type}</li>`).join('')}
908
+ </ul>
909
+ `;
910
+
911
+ // Display other sections
912
+ displayViewingPatterns(analytics.viewing_patterns);
913
+ displayActivityTimeline(analytics.lead_timeline);
914
+ displayPriceAnalysis(analytics.price_preferences);
915
+ displayPropertiesViewed(data.properties);
916
+ displayAIRecommendations(analytics);
917
+ }
918
+
919
+ function displayViewingPatterns(patterns) {
920
+ const patternsHtml = `
921
+ <div class="d-flex flex-column gap-2">
922
+ <div><strong>Peak Time:</strong> ${patterns.peak_viewing_time}</div>
923
+ <div><strong>Morning Views:</strong> ${patterns.morning_views}</div>
924
+ <div><strong>Afternoon Views:</strong> ${patterns.afternoon_views}</div>
925
+ <div><strong>Evening Views:</strong> ${patterns.evening_views}</div>
926
+ </div>
927
+ `;
928
+ document.getElementById('viewingPatterns').innerHTML = patternsHtml;
929
+ }
930
+
931
+ function displayActivityTimeline(timeline) {
932
+ const timelineHtml = timeline.slice(0, 5).map(item => `
933
+ <div class="border rounded p-2 mb-2">
934
+ <div><strong>${item.property_name}</strong></div>
935
+ <small class="text-muted">${item.views} views β€’ ${new Date(item.date).toLocaleDateString()}</small>
936
+ </div>
937
+ `).join('');
938
+ document.getElementById('activityTimeline').innerHTML = timelineHtml;
939
+ }
940
+
941
+ function displayPriceAnalysis(pricePrefs) {
942
+ const priceHtml = `
943
+ <div class="row">
944
+ <div class="col-md-3">
945
+ <div class="text-center">
946
+ <div class="h4 text-primary">β‚Ή${formatPrice(pricePrefs.avg_price)}</div>
947
+ <small>Average Price</small>
948
+ </div>
949
+ </div>
950
+ <div class="col-md-3">
951
+ <div class="text-center">
952
+ <div class="h4 text-success">β‚Ή${formatPrice(pricePrefs.min_price)}</div>
953
+ <small>Min Price</small>
954
+ </div>
955
+ </div>
956
+ <div class="col-md-3">
957
+ <div class="text-center">
958
+ <div class="h4 text-warning">β‚Ή${formatPrice(pricePrefs.max_price)}</div>
959
+ <small>Max Price</small>
960
+ </div>
961
+ </div>
962
+ <div class="col-md-3">
963
+ <div class="text-center">
964
+ <div class="h4 text-info">β‚Ή${formatPrice(pricePrefs.price_range)}</div>
965
+ <small>Price Range</small>
966
+ </div>
967
+ </div>
968
+ </div>
969
+ `;
970
+ document.getElementById('priceAnalysis').innerHTML = priceHtml;
971
+ }
972
+
973
+ function displayPropertiesViewed(properties) {
974
+ const propertiesHtml = properties.slice(0, 6).map(prop => `
975
+ <div class="col-md-4 mb-3">
976
+ <div class="property-item">
977
+ <div class="property-name">${prop.propertyName}</div>
978
+ <div class="property-details">
979
+ <div><span>Type:</span> ${prop.propertyTypeName}</div>
980
+ <div><span>Price:</span> β‚Ή${formatPrice(prop.price)}</div>
981
+ <div><span>Views:</span> ${prop.viewCount}</div>
982
+ <div><span>Engagement:</span> ${Math.round(prop.engagement_score)}</div>
983
+ </div>
984
+ </div>
985
+ </div>
986
+ `).join('');
987
+ document.getElementById('propertiesViewed').innerHTML = propertiesHtml;
988
+ }
989
+
990
+ function displayAIRecommendations(analytics) {
991
+ const recommendationsHtml = `
992
+ <div class="row">
993
+ <div class="col-md-6">
994
+ <h6>AI Recommendations:</h6>
995
+ <ul>
996
+ ${analytics.recommendations.map(rec => `<li>${rec}</li>`).join('')}
997
+ </ul>
998
+ </div>
999
+ <div class="col-md-6">
1000
+ <h6>Risk Assessment:</h6>
1001
+ <div><strong>Risk Level:</strong> ${analytics.risk_assessment.risk_level}</div>
1002
+ <div><strong>Opportunity Score:</strong> ${Math.round(analytics.opportunity_score)}</div>
1003
+ </div>
1004
+ </div>
1005
+ `;
1006
+ document.getElementById('aiRecommendations').innerHTML = recommendationsHtml;
1007
+ }
1008
+
1009
+ // Multi-AI Functions
1010
+ async function sendMultiAIRecommendations() {
1011
+ const customerId = document.getElementById('customerId').value;
1012
+ const email = document.getElementById('multiAiEmail').value;
1013
+ const emailCount = document.getElementById('emailCount').value;
1014
+
1015
+ if (!customerId) {
1016
+ showError('Please enter a customer ID first');
1017
+ return;
1018
+ }
1019
+
1020
+ try {
1021
+ showSuccess('πŸ€– Starting Multi-AI recommendation process...');
1022
+
1023
+ const response = await fetch(`/api/multi-ai-recommendations/${customerId}`, {
1024
+ method: 'POST',
1025
+ headers: {
1026
+ 'Content-Type': 'application/json',
1027
+ },
1028
+ body: JSON.stringify({
1029
+ email: email,
1030
+ email_count: parseInt(emailCount)
1031
+ })
1032
+ });
1033
+
1034
+ const result = await response.json();
1035
+
1036
+ if (result.success) {
1037
+ displayMultiAIResults(result);
1038
+ showSuccess(`βœ… Successfully sent ${result.total_sent} AI recommendation emails!`);
1039
+ } else {
1040
+ showError(`Failed to send multi-AI recommendations: ${result.error}`);
1041
+ }
1042
+ } catch (error) {
1043
+ console.error('Error sending multi-AI recommendations:', error);
1044
+ showError('Error sending multi-AI recommendations. Please try again.');
1045
+ }
1046
+ }
1047
+
1048
+ function displayMultiAIResults(result) {
1049
+ const resultsHtml = `
1050
+ <div class="alert alert-success">
1051
+ <h5><i class="fas fa-check-circle"></i> Multi-AI Recommendations Results</h5>
1052
+ <div class="row">
1053
+ <div class="col-md-6">
1054
+ <p><strong>Customer ID:</strong> ${result.customer_id}</p>
1055
+ <p><strong>Recipient:</strong> ${result.recipient_email}</p>
1056
+ <p><strong>Total Sent:</strong> ${result.total_sent} emails</p>
1057
+ <p><strong>Total Failed:</strong> ${result.total_failed} emails</p>
1058
+ </div>
1059
+ <div class="col-md-6">
1060
+ <p><strong>AI Insights:</strong></p>
1061
+ <ul>
1062
+ <li>Personality: ${result.ai_insights?.personality_type || 'Analytical'}</li>
1063
+ <li>Urgency: ${result.ai_insights?.urgency_level || 'Medium'}</li>
1064
+ <li>Decision Style: ${result.ai_insights?.decision_making_style || 'Data-driven'}</li>
1065
+ </ul>
1066
+ </div>
1067
+ </div>
1068
+ </div>
1069
+
1070
+ <div class="row">
1071
+ <div class="col-md-6">
1072
+ <h6><i class="fas fa-check"></i> Successfully Sent Emails:</h6>
1073
+ ${result.sent_emails.map(email => `
1074
+ <div class="alert alert-success">
1075
+ <strong>${email.type}:</strong> ${email.subject}
1076
+ <br><small>Recommendations: ${email.recommendations_count}</small>
1077
+ </div>
1078
+ `).join('')}
1079
+ </div>
1080
+ <div class="col-md-6">
1081
+ <h6><i class="fas fa-exclamation-triangle"></i> Failed Emails:</h6>
1082
+ ${result.failed_emails.length > 0 ? result.failed_emails.map(email => `
1083
+ <div class="alert alert-warning">
1084
+ <strong>${email.type}:</strong> ${email.error}
1085
+ </div>
1086
+ `).join('') : '<div class="alert alert-success">No failed emails!</div>'}
1087
+ </div>
1088
+ </div>
1089
+ `;
1090
+
1091
+ document.getElementById('multiAiResults').innerHTML = resultsHtml;
1092
+ }
1093
+
1094
+ async function getChromaDBRecommendations() {
1095
+ const customerId = document.getElementById('customerId').value;
1096
+
1097
+ if (!customerId) {
1098
+ showError('Please enter a customer ID first');
1099
+ return;
1100
+ }
1101
+
1102
+ try {
1103
+ showSuccess('πŸ” Getting ChromaDB recommendations...');
1104
+
1105
+ const response = await fetch(`/api/chromadb-recommendations/${customerId}`);
1106
+ const result = await response.json();
1107
+
1108
+ if (result.success) {
1109
+ displayChromaDBResults(result);
1110
+ showSuccess('βœ… ChromaDB recommendations loaded successfully!');
1111
+ } else {
1112
+ showError(`Failed to get ChromaDB recommendations: ${result.error}`);
1113
+ }
1114
+ } catch (error) {
1115
+ console.error('Error getting ChromaDB recommendations:', error);
1116
+ showError('Error getting ChromaDB recommendations. Please try again.');
1117
+ }
1118
+ }
1119
+
1120
+ function displayChromaDBResults(result) {
1121
+ const resultsHtml = `
1122
+ <div class="alert alert-info">
1123
+ <h5><i class="fas fa-database"></i> ChromaDB Recommendations Results</h5>
1124
+ <p><strong>Total Recommendations:</strong> ${result.total_recommendations}</p>
1125
+ <p><strong>Customer ID:</strong> ${result.customer_id}</p>
1126
+ </div>
1127
+
1128
+ <div class="row">
1129
+ ${Object.entries(result.recommendations).map(([type, recs]) => `
1130
+ <div class="col-md-4">
1131
+ <div class="card">
1132
+ <div class="card-header">
1133
+ <h6><i class="fas fa-star"></i> ${type.replace('_', ' ').toUpperCase()}</h6>
1134
+ </div>
1135
+ <div class="card-body">
1136
+ ${recs.slice(0, 3).map(rec => `
1137
+ <div class="property-item mb-2">
1138
+ <div class="property-name">${rec.property_name || 'Property'}</div>
1139
+ <div class="property-details">
1140
+ <span>Price:</span> β‚Ή${formatPrice(rec.price || 0)}<br>
1141
+ <span>Type:</span> ${rec.property_type || 'N/A'}<br>
1142
+ <span>AI Score:</span> ${(rec.ai_score || 0).toFixed(2)}
1143
+ </div>
1144
+ ${rec.ai_explanation ? `<small class="text-muted">${rec.ai_explanation}</small>` : ''}
1145
+ </div>
1146
+ `).join('')}
1147
+ ${recs.length > 3 ? `<small class="text-muted">... and ${recs.length - 3} more</small>` : ''}
1148
+ </div>
1149
+ </div>
1150
+ </div>
1151
+ `).join('')}
1152
+ </div>
1153
+ `;
1154
+
1155
+ document.getElementById('chromaDbResults').innerHTML = resultsHtml;
1156
+ }
1157
+
1158
+ async function fetchAllProperties() {
1159
+ try {
1160
+ showSuccess('πŸ“₯ Starting property fetch to ChromaDB...');
1161
+
1162
+ const response = await fetch('/api/fetch-all-properties?workers=10&page_size=100&max_pages=10');
1163
+ const result = await response.json();
1164
+
1165
+ if (result.success) {
1166
+ showSuccess(`βœ… Successfully fetched ${result.total_properties} properties to ChromaDB!`);
1167
+ } else {
1168
+ showError(`Failed to fetch properties: ${result.error}`);
1169
+ }
1170
+ } catch (error) {
1171
+ console.error('Error fetching properties:', error);
1172
+ showError('Error fetching properties. Please try again.');
1173
+ }
1174
+ }
1175
+
1176
+ // Email Functions
1177
+ async function sendTestEmail() {
1178
+ const email = document.getElementById('testEmail').value;
1179
+ if (!email) {
1180
+ showError('Please enter an email address');
1181
+ return;
1182
+ }
1183
+
1184
+ try {
1185
+ const response = await fetch('/api/test-email', {
1186
+ method: 'POST',
1187
+ headers: {
1188
+ 'Content-Type': 'application/json'
1189
+ },
1190
+ body: JSON.stringify({
1191
+ customer_id: currentCustomerId,
1192
+ email_type: 'test_email',
1193
+ recipient_email: email
1194
+ })
1195
+ });
1196
+
1197
+ const data = await response.json();
1198
+
1199
+ if (data.success) {
1200
+ showSuccess('Test email sent successfully!');
1201
+ loadEmailLogs();
1202
+ } else {
1203
+ showError('Email send failed: ' + (data.error || 'Unknown error'));
1204
+ }
1205
+ } catch (error) {
1206
+ showError('Error sending email: ' + error.message);
1207
+ }
1208
+ }
1209
+
1210
+ async function testEmailTriggers() {
1211
+ try {
1212
+ const response = await fetch(`/api/test-automated-emails/${currentCustomerId}`, {
1213
+ method: 'POST',
1214
+ headers: {
1215
+ 'Content-Type': 'application/json'
1216
+ }
1217
+ });
1218
+
1219
+ const data = await response.json();
1220
+
1221
+ if (data.success) {
1222
+ showSuccess('Email triggers test successful!');
1223
+ loadEmailLogs();
1224
+ } else {
1225
+ showError('Email triggers test failed: ' + (data.error || 'Unknown error'));
1226
+ }
1227
+ } catch (error) {
1228
+ showError('Error testing email triggers: ' + error.message);
1229
+ }
1230
+ }
1231
+
1232
+ async function sendDirectTestEmail() {
1233
+ const recipient = document.getElementById('testEmail').value;
1234
+ if (!recipient) {
1235
+ showError('Please enter a test email address');
1236
+ return;
1237
+ }
1238
+
1239
+ try {
1240
+ const response = await fetch('/api/test-email-simple', {
1241
+ method: 'GET'
1242
+ });
1243
+
1244
+ const data = await response.json();
1245
+
1246
+ if (data.success) {
1247
+ showSuccess('Direct test email sent successfully!');
1248
+ loadEmailLogs();
1249
+ } else {
1250
+ showError('Direct test email failed: ' + (data.error || 'Unknown error'));
1251
+ }
1252
+ } catch (error) {
1253
+ showError('Error sending direct test email: ' + error.message);
1254
+ }
1255
+ }
1256
+
1257
+ async function loadEmailLogs() {
1258
+ try {
1259
+ const response = await fetch('/api/list-emails');
1260
+ const data = await response.json();
1261
+
1262
+ if (data.success) {
1263
+ const emails = data.emails || [];
1264
+
1265
+ // Update email dashboard metrics
1266
+ document.getElementById('totalEmails').textContent = emails.length;
1267
+ document.getElementById('successEmails').textContent = emails.filter(e => e.success !== false).length;
1268
+ document.getElementById('failedEmails').textContent = emails.filter(e => e.success === false).length;
1269
+ document.getElementById('scheduledEmails').textContent = emails.filter(e => e.email_type === 'scheduled').length;
1270
+
1271
+ document.getElementById('emailLogs').innerHTML = `
1272
+ ${emails.slice(0, 5).map(email => `
1273
+ <div class="alert ${email.success !== false ? 'alert-success' : 'alert-danger'}">
1274
+ <div class="d-flex justify-content-between">
1275
+ <strong>${email.filename || 'Email Log'}</strong>
1276
+ <small>${new Date(email.created || Date.now()).toLocaleString()}</small>
1277
+ </div>
1278
+ <div>Size: ${email.size ? (email.size/1024).toFixed(1) + ' KB' : 'N/A'}</div>
1279
+ </div>
1280
+ `).join('')}
1281
+ ${emails.length === 0 ? '<div class="alert alert-info">No email logs found</div>' : ''}
1282
+ `;
1283
+ } else {
1284
+ document.getElementById('emailLogs').innerHTML = '<div class="alert alert-danger">Failed to load email logs</div>';
1285
+ }
1286
+ } catch (error) {
1287
+ document.getElementById('emailLogs').innerHTML = '<div class="alert alert-danger">Error loading email logs</div>';
1288
+ }
1289
+ }
1290
+
1291
+ async function testAllEmailTypes() {
1292
+ showSuccess('Testing all email types... This may take a moment.');
1293
+ // Implementation for testing all email types
1294
+ }
1295
+
1296
+ async function clearEmailLogs() {
1297
+ if (confirm('Are you sure you want to clear all email logs?')) {
1298
+ showSuccess('Email logs cleared successfully!');
1299
+ loadEmailLogs();
1300
+ }
1301
+ }
1302
+
1303
+ async function sendAllIntendedEmails() {
1304
+ showSuccess('Sending all intended emails...');
1305
+ // Implementation for sending all intended emails
1306
+ }
1307
+
1308
+ async function sendMultiAIEmails() {
1309
+ showSuccess('Starting Multi-AI email campaign...');
1310
+ // Implementation for sending multi-AI emails
1311
+ }
1312
+
1313
+ // New Email Testing Functions
1314
+ async function testSendGridConnection() {
1315
+ try {
1316
+ showSuccess('Testing SendGrid connection...');
1317
+ const response = await fetch('/api/test-sendgrid-connection');
1318
+ const result = await response.json();
1319
+
1320
+ if (result.success) {
1321
+ showSuccess(`βœ… SendGrid connection successful: ${result.message}`);
1322
+ document.getElementById('multiAiResults').innerHTML = `
1323
+ <div class="alert alert-success">
1324
+ <h5><i class="fas fa-check-circle"></i> SendGrid Connection Test</h5>
1325
+ <p><strong>Status:</strong> Connected βœ…</p>
1326
+ <p><strong>Details:</strong> ${JSON.stringify(result.details, null, 2)}</p>
1327
+ <p><strong>Timestamp:</strong> ${result.timestamp}</p>
1328
+ </div>
1329
+ `;
1330
+ } else {
1331
+ showError(`❌ SendGrid connection failed: ${result.error}`);
1332
+ }
1333
+ } catch (error) {
1334
+ showError(`❌ Error testing SendGrid: ${error.message}`);
1335
+ }
1336
+ }
1337
+
1338
+ async function getEmailAnalysisBasis() {
1339
+ try {
1340
+ const customerId = document.getElementById('customerId').value || 144;
1341
+ showSuccess('Getting email analysis basis...');
1342
+
1343
+ const response = await fetch(`/api/email-analysis-basis/${customerId}`);
1344
+ const result = await response.json();
1345
+
1346
+ if (result.success) {
1347
+ document.getElementById('multiAiResults').innerHTML = `
1348
+ <div class="alert alert-info">
1349
+ <h5><i class="fas fa-search-plus"></i> Email Analysis Basis for Customer ${customerId}</h5>
1350
+ <div class="row">
1351
+ <div class="col-md-6">
1352
+ <h6>Customer Preferences:</h6>
1353
+ <pre>${JSON.stringify(result.analysis_basis.customer_preferences, null, 2)}</pre>
1354
+ </div>
1355
+ <div class="col-md-6">
1356
+ <h6>Email Triggers:</h6>
1357
+ ${Object.entries(result.email_triggers).map(([type, trigger]) =>
1358
+ `<p><strong>${type}:</strong> ${trigger}</p>`
1359
+ ).join('')}
1360
+ </div>
1361
+ </div>
1362
+ <div class="row mt-3">
1363
+ <div class="col-12">
1364
+ <h6>AI Insights Summary:</h6>
1365
+ <p><strong>Engagement Level:</strong> ${result.analysis_basis.engagement_level}</p>
1366
+ <p><strong>Conversion Probability:</strong> ${(result.analysis_basis.conversion_probability * 100).toFixed(1)}%</p>
1367
+ </div>
1368
+ </div>
1369
+ </div>
1370
+ `;
1371
+ showSuccess('βœ… Email analysis basis retrieved successfully');
1372
+ } else {
1373
+ showError(`❌ Failed to get analysis basis: ${result.error}`);
1374
+ }
1375
+ } catch (error) {
1376
+ showError(`❌ Error getting analysis basis: ${error.message}`);
1377
+ }
1378
+ }
1379
+
1380
+ async function previewAllEmailContent() {
1381
+ try {
1382
+ const customerId = document.getElementById('customerId').value || 144;
1383
+ showSuccess('Previewing all 10 email contents...');
1384
+
1385
+ const response = await fetch(`/api/email-content-preview/${customerId}`);
1386
+ const result = await response.json();
1387
+
1388
+ if (result.success) {
1389
+ let previewHtml = `
1390
+ <div class="alert alert-warning">
1391
+ <h5><i class="fas fa-file-alt"></i> All 10 Email Content Previews</h5>
1392
+ <p><strong>Total Emails:</strong> ${result.total_emails}</p>
1393
+ </div>
1394
+ `;
1395
+
1396
+ result.email_previews.forEach((preview, index) => {
1397
+ previewHtml += `
1398
+ <div class="card mb-3">
1399
+ <div class="card-header">
1400
+ <h6>${index + 1}. ${preview.email_type.replace('_', ' ').toUpperCase()}</h6>
1401
+ <p class="mb-0"><strong>Subject:</strong> ${preview.subject}</p>
1402
+ </div>
1403
+ <div class="card-body">
1404
+ ${preview.error ?
1405
+ `<div class="alert alert-danger">Error: ${preview.error}</div>` :
1406
+ `
1407
+ <p><strong>Properties Included:</strong> ${preview.recommendations_count}</p>
1408
+ <div class="row">
1409
+ <div class="col-md-6">
1410
+ <h6>Sample Properties:</h6>
1411
+ ${preview.properties_included.map(prop =>
1412
+ `<p>β€’ ${prop.name} - β‚Ή${prop.price} (${prop.type}) - Score: ${prop.score}</p>`
1413
+ ).join('')}
1414
+ </div>
1415
+ <div class="col-md-6">
1416
+ <h6>Email Content Preview:</h6>
1417
+ <div style="max-height: 150px; overflow-y: auto; border: 1px solid #ddd; padding: 10px;">
1418
+ ${preview.text_content.substring(0, 300)}...
1419
+ </div>
1420
+ </div>
1421
+ </div>
1422
+ `
1423
+ }
1424
+ </div>
1425
+ </div>
1426
+ `;
1427
+ });
1428
+
1429
+ document.getElementById('multiAiResults').innerHTML = previewHtml;
1430
+ showSuccess('βœ… All email content previews generated successfully');
1431
+ } else {
1432
+ showError(`❌ Failed to preview email content: ${result.error}`);
1433
+ }
1434
+ } catch (error) {
1435
+ showError(`❌ Error previewing email content: ${error.message}`);
1436
+ }
1437
+ }
1438
+
1439
+ async function testAll10Emails() {
1440
+ try {
1441
+ const customerId = document.getElementById('customerId').value || 144;
1442
+ const email = document.getElementById('testEmail').value;
1443
+
1444
+ if (!email) {
1445
+ showError('Please enter an email address');
1446
+ return;
1447
+ }
1448
+
1449
+ showSuccess('Preparing to send all 10 email types... Ensuring properties are available...');
1450
+
1451
+ // First, make sure properties are fetched
1452
+ try {
1453
+ const fetchResponse = await fetch('/api/fetch-all-properties');
1454
+ const fetchResult = await fetchResponse.json();
1455
+ if (fetchResult.success) {
1456
+ showSuccess(`βœ… Properties ready: ${fetchResult.summary.total_stored} properties available`);
1457
+ }
1458
+ } catch (fetchError) {
1459
+ showError('Warning: Could not verify property availability, continuing anyway...');
1460
+ }
1461
+
1462
+ showSuccess('Sending all 10 email types with real properties... This may take a few minutes.');
1463
+
1464
+ const response = await fetch(`/api/test-all-10-emails/${customerId}`, {
1465
+ method: 'POST',
1466
+ headers: {
1467
+ 'Content-Type': 'application/json',
1468
+ },
1469
+ body: JSON.stringify({ email: email })
1470
+ });
1471
+
1472
+ const result = await response.json();
1473
+
1474
+ if (result.success) {
1475
+ let resultsHtml = `
1476
+ <div class="alert alert-success">
1477
+ <h5><i class="fas fa-envelope-open-text"></i> All 10 Email Types Test Results</h5>
1478
+ <p><strong>Customer ID:</strong> ${result.customer_id}</p>
1479
+ <p><strong>Recipient:</strong> ${result.recipient_email}</p>
1480
+ <p><strong>Total Attempted:</strong> ${result.test_results.total_attempted}</p>
1481
+ <p><strong>Successfully Sent:</strong> ${result.test_results.total_sent}</p>
1482
+ <p><strong>Failed:</strong> ${result.test_results.total_failed}</p>
1483
+ <p><strong>ChromaDB Status:</strong> ${result.chromadb_status}</p>
1484
+ </div>
1485
+ `;
1486
+
1487
+ if (result.test_results.sent_emails.length > 0) {
1488
+ resultsHtml += '<h6>βœ… Successfully Sent Emails:</h6>';
1489
+ result.test_results.sent_emails.forEach((email, index) => {
1490
+ resultsHtml += `
1491
+ <div class="card mb-2">
1492
+ <div class="card-body">
1493
+ <h6>${index + 1}. ${email.type.replace('_', ' ').toUpperCase()}</h6>
1494
+ <p><strong>Subject:</strong> ${email.subject}</p>
1495
+ <p><strong>Strategy:</strong> ${email.strategy}</p>
1496
+ <p><strong>Properties:</strong> ${email.recommendations_count} from ChromaDB</p>
1497
+ <p><strong>Sample Properties:</strong></p>
1498
+ <ul>
1499
+ ${email.sample_properties.map(prop =>
1500
+ `<li>${prop.name} - β‚Ή${prop.price} (${prop.type}) - AI Score: ${prop.ai_score}</li>`
1501
+ ).join('')}
1502
+ </ul>
1503
+ <small class="text-muted">Sent at: ${email.timestamp}</small>
1504
+ </div>
1505
+ </div>
1506
+ `;
1507
+ });
1508
+ }
1509
+
1510
+ if (result.test_results.failed_emails.length > 0) {
1511
+ resultsHtml += '<h6>❌ Failed Emails:</h6>';
1512
+ result.test_results.failed_emails.forEach((email, index) => {
1513
+ resultsHtml += `
1514
+ <div class="alert alert-danger">
1515
+ <strong>${index + 1}. ${email.type}:</strong> ${email.error}
1516
+ <br><small>Strategy: ${email.strategy}</small>
1517
+ </div>
1518
+ `;
1519
+ });
1520
+ }
1521
+
1522
+ document.getElementById('multiAiResults').innerHTML = resultsHtml;
1523
+ showSuccess(`βœ… Email test completed! ${result.test_results.total_sent}/${result.test_results.total_attempted} emails sent successfully.`);
1524
+ } else {
1525
+ showError(`❌ Failed to test emails: ${result.error}`);
1526
+ }
1527
+ } catch (error) {
1528
+ showError(`❌ Error testing emails: ${error.message}`);
1529
+ }
1530
+ }
1531
+
1532
+ async function checkChromaDBStatus() {
1533
+ try {
1534
+ showSuccess('Checking ChromaDB status...');
1535
+
1536
+ const response = await fetch('/api/chromadb-status');
1537
+ const result = await response.json();
1538
+
1539
+ if (result.success) {
1540
+ let statusHtml = `
1541
+ <div class="alert alert-${result.chromadb_initialized ? (result.property_count > 0 ? 'success' : 'warning') : 'danger'}">
1542
+ <h5><i class="fas fa-database"></i> ChromaDB Status Report</h5>
1543
+ <p><strong>Initialized:</strong> ${result.chromadb_initialized ? 'βœ… Yes' : '❌ No'}</p>
1544
+ `;
1545
+
1546
+ if (result.chromadb_initialized) {
1547
+ statusHtml += `
1548
+ <p><strong>Collection Name:</strong> ${result.collection_name}</p>
1549
+ <p><strong>Property Count:</strong> ${result.property_count}</p>
1550
+ <p><strong>Status:</strong> ${result.status === 'ready' ? 'βœ… Ready' : '⚠️ Empty'}</p>
1551
+ `;
1552
+ }
1553
+
1554
+ statusHtml += `
1555
+ <p><strong>Message:</strong> ${result.message}</p>
1556
+ <p><strong>Timestamp:</strong> ${result.timestamp}</p>
1557
+ </div>
1558
+ `;
1559
+
1560
+ document.getElementById('multiAiResults').innerHTML = statusHtml;
1561
+
1562
+ if (result.property_count > 0) {
1563
+ showSuccess(`βœ… ChromaDB ready with ${result.property_count} properties!`);
1564
+ } else {
1565
+ showError(`⚠️ ChromaDB is empty. Click "Fetch Properties" to populate it.`);
1566
+ }
1567
+ } else {
1568
+ showError(`❌ ChromaDB status check failed: ${result.error}`);
1569
+ }
1570
+ } catch (error) {
1571
+ showError(`❌ Error checking ChromaDB status: ${error.message}`);
1572
+ }
1573
+ }
1574
+
1575
+ async function showEmailTypes() {
1576
+ const emailTypes = [
1577
+ { type: 'property_based', description: 'Properties matching customer\'s preferred property types', icon: '🏠' },
1578
+ { type: 'price_based', description: 'Properties within customer\'s budget range', icon: 'πŸ’°' },
1579
+ { type: 'location_based', description: 'Properties in customer\'s preferred locations', icon: 'πŸ“' },
1580
+ { type: 'similarity_based', description: 'Properties similar to customer\'s viewed properties', icon: 'πŸ”' },
1581
+ { type: 'behavioral_based', description: 'Properties based on customer behavior patterns', icon: '🧠' },
1582
+ { type: 'premium_properties', description: 'High-end luxury properties', icon: '⭐' },
1583
+ { type: 'budget_friendly', description: 'Value-for-money properties', icon: 'πŸ’΅' },
1584
+ { type: 'trending_properties', description: 'Currently trending market properties', icon: 'πŸ“ˆ' },
1585
+ { type: 'family_oriented', description: 'Family-suitable properties with amenities', icon: 'πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦' },
1586
+ { type: 'investment_opportunities', description: 'Properties with high ROI potential', icon: 'πŸ’Ό' }
1587
+ ];
1588
+
1589
+ let typesHtml = `
1590
+ <div class="alert alert-info">
1591
+ <h5><i class="fas fa-list"></i> All 10 Email Types & ChromaDB Strategies</h5>
1592
+ <p>Each email type uses unique ChromaDB vector search strategies:</p>
1593
+ </div>
1594
+ `;
1595
+
1596
+ emailTypes.forEach((emailType, index) => {
1597
+ typesHtml += `
1598
+ <div class="card mb-2">
1599
+ <div class="card-body">
1600
+ <h6>${emailType.icon} ${index + 1}. ${emailType.type.replace('_', ' ').toUpperCase()}</h6>
1601
+ <p class="mb-0">${emailType.description}</p>
1602
+ </div>
1603
+ </div>
1604
+ `;
1605
+ });
1606
+
1607
+ document.getElementById('multiAiResults').innerHTML = typesHtml;
1608
+ showSuccess('βœ… All email types displayed');
1609
+ }
1610
+
1611
+ // Utility Functions
1612
+ function formatPrice(price) {
1613
+ return new Intl.NumberFormat('en-IN').format(price);
1614
+ }
1615
+
1616
+ function formatDuration(seconds) {
1617
+ const hours = Math.floor(seconds / 3600);
1618
+ const minutes = Math.floor((seconds % 3600) / 60);
1619
+ return `${hours}h ${minutes}m`;
1620
+ }
1621
+
1622
+ function showSuccess(message) {
1623
+ const alert = document.createElement('div');
1624
+ alert.className = 'alert alert-success alert-dismissible fade show position-fixed';
1625
+ alert.style.cssText = 'top: 20px; right: 20px; z-index: 9999; max-width: 400px;';
1626
+ alert.innerHTML = `
1627
+ ${message}
1628
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
1629
+ `;
1630
+ document.body.appendChild(alert);
1631
+
1632
+ setTimeout(() => {
1633
+ if (alert.parentNode) {
1634
+ alert.remove();
1635
+ }
1636
+ }, 5000);
1637
+ }
1638
+
1639
+ function showError(message) {
1640
+ const alert = document.createElement('div');
1641
+ alert.className = 'alert alert-danger alert-dismissible fade show position-fixed';
1642
+ alert.style.cssText = 'top: 20px; right: 20px; z-index: 9999; max-width: 400px;';
1643
+ alert.innerHTML = `
1644
+ ${message}
1645
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
1646
+ `;
1647
+ document.body.appendChild(alert);
1648
+
1649
+ setTimeout(() => {
1650
+ if (alert.parentNode) {
1651
+ alert.remove();
1652
+ }
1653
+ }, 5000);
1654
+ }
1655
+
1656
+ // List saved emails function
1657
+ function listSavedEmails() {
1658
+ console.log('Getting saved emails...');
1659
+ document.getElementById('emailTestResults').innerHTML = '<div class="alert alert-info"><i class="fas fa-spinner fa-spin"></i> Loading saved emails...</div>';
1660
+
1661
+ fetch(`${API_BASE_URL}/api/saved-emails`)
1662
+ .then(response => response.json())
1663
+ .then(data => {
1664
+ let html = '<div class="alert alert-success"><h5><i class="fas fa-folder-open"></i> Saved Emails</h5>';
1665
+
1666
+ if (data.saved_emails && data.saved_emails.length > 0) {
1667
+ html += `<p><strong>Found ${data.total_count} saved email files:</strong></p>`;
1668
+ html += '<div class="row">';
1669
+
1670
+ data.saved_emails.forEach(email => {
1671
+ html += `
1672
+ <div class="col-md-6 mb-3">
1673
+ <div class="card">
1674
+ <div class="card-body">
1675
+ <h6 class="card-title">${email.email_type}</h6>
1676
+ <p class="card-text">
1677
+ <small class="text-muted">
1678
+ πŸ“… ${email.created_time}<br>
1679
+ πŸ“ ${email.size_kb} KB
1680
+ </small>
1681
+ </p>
1682
+ <a href="${API_BASE_URL}/view-email/${email.filename}" target="_blank" class="btn btn-primary btn-sm">
1683
+ <i class="fas fa-external-link-alt"></i> Open Email
1684
+ </a>
1685
+ </div>
1686
+ </div>
1687
+ </div>
1688
+ `;
1689
+ });
1690
+
1691
+ html += '</div>';
1692
+ } else {
1693
+ html += '<p>No saved emails found. Emails will be saved here when SendGrid limits are exceeded.</p>';
1694
+ }
1695
+
1696
+ html += '</div>';
1697
+ document.getElementById('emailTestResults').innerHTML = html;
1698
+ })
1699
+ .catch(error => {
1700
+ console.error('Error:', error);
1701
+ document.getElementById('emailTestResults').innerHTML =
1702
+ '<div class="alert alert-danger"><i class="fas fa-exclamation-triangle"></i> Error loading saved emails: ' + error.message + '</div>';
1703
+ });
1704
+ }
1705
+
1706
+ // Auto-analyze customer 144 on page load
1707
+ window.onload = function() {
1708
+ analyzeCustomer();
1709
+ };
1710
+ </script>
1711
+
1712
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
1713
+ </body>
1714
+ </html>
templates/analytics.html ADDED
@@ -0,0 +1,919 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Lead Qualification Analytics Dashboard</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
9
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
10
+ <style>
11
+ body {
12
+ background-color: #f8f9fa;
13
+ padding-top: 2rem;
14
+ }
15
+ .header {
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ color: white;
18
+ padding: 2rem 0;
19
+ margin-bottom: 2rem;
20
+ }
21
+ .stats-card {
22
+ background: white;
23
+ padding: 1.5rem;
24
+ border-radius: 10px;
25
+ box-shadow: 0 0 10px rgba(0,0,0,0.1);
26
+ margin-bottom: 1rem;
27
+ text-align: center;
28
+ }
29
+ .stats-number {
30
+ font-size: 2.5rem;
31
+ font-weight: bold;
32
+ color: #007bff;
33
+ }
34
+ .stats-label {
35
+ color: #6c757d;
36
+ font-size: 1rem;
37
+ margin-top: 0.5rem;
38
+ }
39
+ .chart-container {
40
+ background: white;
41
+ padding: 2rem;
42
+ border-radius: 10px;
43
+ box-shadow: 0 0 10px rgba(0,0,0,0.1);
44
+ margin-bottom: 2rem;
45
+ }
46
+ .activity-list {
47
+ background: white;
48
+ padding: 2rem;
49
+ border-radius: 10px;
50
+ box-shadow: 0 0 10px rgba(0,0,0,0.1);
51
+ margin-bottom: 2rem;
52
+ }
53
+ .activity-item {
54
+ padding: 1rem;
55
+ border-bottom: 1px solid #e9ecef;
56
+ display: flex;
57
+ justify-content: space-between;
58
+ align-items: center;
59
+ }
60
+ .activity-item:last-child {
61
+ border-bottom: none;
62
+ }
63
+ .activity-icon {
64
+ width: 40px;
65
+ height: 40px;
66
+ border-radius: 50%;
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ color: white;
71
+ font-size: 1.2rem;
72
+ }
73
+ .activity-icon.click {
74
+ background: #28a745;
75
+ }
76
+ .activity-icon.search {
77
+ background: #007bff;
78
+ }
79
+ .activity-icon.visit {
80
+ background: #ffc107;
81
+ }
82
+ .activity-icon.email {
83
+ background: #6c757d;
84
+ }
85
+ .clear-data-btn {
86
+ background: #dc3545;
87
+ color: white;
88
+ border: none;
89
+ padding: 0.5rem 1rem;
90
+ border-radius: 5px;
91
+ cursor: pointer;
92
+ }
93
+ .clear-data-btn:hover {
94
+ background: #c82333;
95
+ }
96
+ .export-btn {
97
+ background: #28a745;
98
+ color: white;
99
+ border: none;
100
+ padding: 0.5rem 1rem;
101
+ border-radius: 5px;
102
+ cursor: pointer;
103
+ }
104
+ .export-btn:hover {
105
+ background: #218838;
106
+ }
107
+ .lead-score-card {
108
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
109
+ color: white;
110
+ padding: 2rem;
111
+ border-radius: 15px;
112
+ text-align: center;
113
+ margin-bottom: 2rem;
114
+ }
115
+ .lead-score-number {
116
+ font-size: 4rem;
117
+ font-weight: bold;
118
+ margin-bottom: 1rem;
119
+ }
120
+ .lead-score-label {
121
+ font-size: 1.2rem;
122
+ opacity: 0.9;
123
+ }
124
+ .lead-status {
125
+ padding: 0.5rem 1rem;
126
+ border-radius: 20px;
127
+ font-weight: bold;
128
+ margin-top: 1rem;
129
+ }
130
+ .lead-status.hot {
131
+ background: #dc3545;
132
+ }
133
+ .lead-status.warm {
134
+ background: #ffc107;
135
+ color: #212529;
136
+ }
137
+ .lead-status.cold {
138
+ background: #6c757d;
139
+ }
140
+ </style>
141
+ </head>
142
+ <body>
143
+ <div class="container-fluid">
144
+ <!-- Header -->
145
+ <div class="header">
146
+ <div class="container">
147
+ <div class="row align-items-center">
148
+ <div class="col-md-8">
149
+ <h1><i class="fas fa-chart-bar"></i> Lead Qualification Analytics Dashboard</h1>
150
+ <p class="mb-0">Track user behavior and lead qualification metrics</p>
151
+ </div>
152
+ <div class="col-md-4 text-end">
153
+ <a href="/" class="btn btn-outline-light me-2">
154
+ <i class="fas fa-home"></i> Home
155
+ </a>
156
+ <button class="btn btn-outline-light me-2" onclick="exportData()">
157
+ <i class="fas fa-download"></i> Export
158
+ </button>
159
+ <button class="btn btn-light" onclick="clearData()">
160
+ <i class="fas fa-trash"></i> Clear
161
+ </button>
162
+ </div>
163
+ </div>
164
+ </div>
165
+ </div>
166
+
167
+ <div class="container">
168
+ <!-- Lead Score Card -->
169
+ <div class="row mb-4">
170
+ <div class="col-md-12">
171
+ <div class="lead-score-card">
172
+ <div class="lead-score-number" id="leadScore">0</div>
173
+ <div class="lead-score-label">Lead Qualification Score</div>
174
+ <div class="lead-status" id="leadStatus">Cold</div>
175
+ </div>
176
+ </div>
177
+ </div>
178
+
179
+ <!-- Action Buttons -->
180
+ <div class="row mb-4">
181
+ <div class="col-md-6">
182
+ <button class="clear-data-btn me-2" onclick="clearData()">
183
+ <i class="fas fa-trash"></i> Clear All Data
184
+ </button>
185
+ <button class="export-btn" onclick="exportData()">
186
+ <i class="fas fa-download"></i> Export Data
187
+ </button>
188
+ </div>
189
+ <div class="col-md-6 text-end">
190
+ <small class="text-muted">Last updated: <span id="lastUpdated"></span></small>
191
+ </div>
192
+ </div>
193
+
194
+ <!-- Stats Cards -->
195
+ <div class="row mb-4">
196
+ <div class="col-md-2">
197
+ <div class="stats-card">
198
+ <div class="stats-number" id="totalClicks">0</div>
199
+ <div class="stats-label">Total Clicks</div>
200
+ </div>
201
+ </div>
202
+ <div class="col-md-2">
203
+ <div class="stats-card">
204
+ <div class="stats-number" id="totalSearches">0</div>
205
+ <div class="stats-label">Total Searches</div>
206
+ </div>
207
+ </div>
208
+ <div class="col-md-2">
209
+ <div class="stats-card">
210
+ <div class="stats-number" id="uniqueProperties">0</div>
211
+ <div class="stats-label">Unique Properties</div>
212
+ </div>
213
+ </div>
214
+ <div class="col-md-2">
215
+ <div class="stats-card">
216
+ <div class="stats-number" id="totalViewTime">0s</div>
217
+ <div class="stats-label">Total View Time</div>
218
+ </div>
219
+ </div>
220
+ <div class="col-md-2">
221
+ <div class="stats-card">
222
+ <div class="stats-number" id="avgViewDuration">0s</div>
223
+ <div class="stats-label">Avg View Duration</div>
224
+ </div>
225
+ </div>
226
+ <div class="col-md-2">
227
+ <div class="stats-card">
228
+ <div class="stats-number" id="visitRequests">0</div>
229
+ <div class="stats-label">Visit Requests</div>
230
+ </div>
231
+ </div>
232
+ </div>
233
+
234
+ <!-- Charts Row -->
235
+ <div class="row mb-4">
236
+ <div class="col-md-6">
237
+ <div class="chart-container">
238
+ <h5><i class="fas fa-chart-pie"></i> Property Type Preferences</h5>
239
+ <canvas id="propertyTypeChart"></canvas>
240
+ </div>
241
+ </div>
242
+ <div class="col-md-6">
243
+ <div class="chart-container">
244
+ <h5><i class="fas fa-chart-bar"></i> Price Range Preferences</h5>
245
+ <canvas id="priceRangeChart"></canvas>
246
+ </div>
247
+ </div>
248
+ </div>
249
+
250
+ <!-- Feature Preferences -->
251
+ <div class="row mb-4">
252
+ <div class="col-md-12">
253
+ <div class="chart-container">
254
+ <h5><i class="fas fa-star"></i> Feature Preferences</h5>
255
+ <canvas id="featureChart"></canvas>
256
+ </div>
257
+ </div>
258
+ </div>
259
+
260
+ <!-- Detailed View Analytics -->
261
+ <div class="row mb-4">
262
+ <div class="col-md-12">
263
+ <div class="chart-container">
264
+ <h5><i class="fas fa-clock"></i> Detailed View Analytics</h5>
265
+ <canvas id="viewDurationChart"></canvas>
266
+ </div>
267
+ </div>
268
+ </div>
269
+
270
+ <!-- Recent Activity -->
271
+ <div class="row">
272
+ <div class="col-md-12">
273
+ <div class="activity-list">
274
+ <h5><i class="fas fa-history"></i> Recent Activity</h5>
275
+ <div id="activityList">
276
+ <p class="text-muted">No activity recorded yet.</p>
277
+ </div>
278
+ </div>
279
+ </div>
280
+ </div>
281
+ </div>
282
+ </div>
283
+
284
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
285
+ <script>
286
+ class LeadAnalyticsDashboard {
287
+ constructor() {
288
+ this.trackingData = this.loadTrackingData();
289
+ this.init();
290
+ }
291
+
292
+ loadTrackingData() {
293
+ const data = localStorage.getItem('userTrackingData');
294
+ return data ? JSON.parse(data) : {
295
+ clickedProperties: [],
296
+ detailedViews: [],
297
+ searchHistory: [],
298
+ features: [],
299
+ priceRanges: [],
300
+ propertyTypes: []
301
+ };
302
+ }
303
+
304
+ init() {
305
+ this.updateStats();
306
+ this.createCharts();
307
+ this.displayRecentActivity();
308
+ this.updateLastUpdated();
309
+ this.updateLeadScore();
310
+ this.generateInsights();
311
+ }
312
+
313
+ updateStats() {
314
+ document.getElementById('totalClicks').textContent = this.trackingData.clickedProperties.length;
315
+ document.getElementById('totalSearches').textContent = this.trackingData.searchHistory.length;
316
+
317
+ // Unique properties clicked
318
+ const uniqueProperties = new Set(this.trackingData.clickedProperties.map(p => p.id)).size;
319
+ document.getElementById('uniqueProperties').textContent = uniqueProperties;
320
+
321
+ // Total view time
322
+ const totalViewTime = this.trackingData.detailedViews.reduce((sum, view) => sum + view.totalDuration, 0);
323
+ document.getElementById('totalViewTime').textContent = this.formatDuration(totalViewTime);
324
+
325
+ // Average view duration
326
+ if (this.trackingData.detailedViews.length > 0) {
327
+ const totalDuration = this.trackingData.detailedViews.reduce((sum, view) => sum + view.totalDuration, 0);
328
+ const totalViews = this.trackingData.detailedViews.reduce((sum, view) => sum + view.viewCount, 0);
329
+ const avgDuration = totalViews > 0 ? Math.round(totalDuration / totalViews) : 0;
330
+ document.getElementById('avgViewDuration').textContent = this.formatDuration(avgDuration);
331
+ }
332
+
333
+ // Visit requests (from localStorage or session)
334
+ const visitHistory = JSON.parse(localStorage.getItem('visitHistory') || '[]');
335
+ document.getElementById('visitRequests').textContent = visitHistory.length;
336
+ }
337
+
338
+ generateInsights() {
339
+ const insights = this.analyzeUserBehavior();
340
+ this.displayInsights(insights);
341
+ }
342
+
343
+ analyzeUserBehavior() {
344
+ const insights = {
345
+ preferredTypes: {},
346
+ preferredPriceRanges: {},
347
+ engagementLevel: 'low',
348
+ sessionDuration: 0,
349
+ topProperties: [],
350
+ recommendations: []
351
+ };
352
+
353
+ // Analyze property type preferences
354
+ this.trackingData.propertyTypes.forEach(type => {
355
+ insights.preferredTypes[type] = (insights.preferredTypes[type] || 0) + 1;
356
+ });
357
+
358
+ // Analyze price range preferences
359
+ this.trackingData.priceRanges.forEach(range => {
360
+ insights.preferredPriceRanges[range] = (insights.preferredPriceRanges[range] || 0) + 1;
361
+ });
362
+
363
+ // Calculate engagement level
364
+ const totalInteractions = this.trackingData.clickedProperties.length + this.trackingData.detailedViews.length;
365
+ const totalTime = this.trackingData.detailedViews.reduce((sum, view) => sum + view.totalDuration, 0);
366
+
367
+ if (totalInteractions >= 10 && totalTime >= 60) {
368
+ insights.engagementLevel = 'high';
369
+ } else if (totalInteractions >= 5 && totalTime >= 30) {
370
+ insights.engagementLevel = 'medium';
371
+ }
372
+
373
+ insights.sessionDuration = totalTime;
374
+
375
+ // Get top properties by engagement
376
+ insights.topProperties = this.trackingData.detailedViews
377
+ .sort((a, b) => b.totalDuration - a.totalDuration)
378
+ .slice(0, 3);
379
+
380
+ // Generate recommendations
381
+ insights.recommendations = this.generateRecommendations();
382
+
383
+ return insights;
384
+ }
385
+
386
+ generateRecommendations() {
387
+ const recommendations = [];
388
+ const insights = this.analyzeUserBehavior();
389
+
390
+ // Recommendation 1: Based on most viewed property type
391
+ const topType = Object.keys(insights.preferredTypes)
392
+ .sort((a, b) => insights.preferredTypes[b] - insights.preferredTypes[a])[0];
393
+
394
+ if (topType) {
395
+ recommendations.push({
396
+ type: 'property_type',
397
+ message: `You show strong interest in ${topType} properties. Consider exploring more ${topType} options.`,
398
+ priority: 'high'
399
+ });
400
+ }
401
+
402
+ // Recommendation 2: Based on price range
403
+ const topPriceRange = Object.keys(insights.preferredPriceRanges)
404
+ .sort((a, b) => insights.preferredPriceRanges[b] - insights.preferredPriceRanges[a])[0];
405
+
406
+ if (topPriceRange) {
407
+ recommendations.push({
408
+ type: 'price_range',
409
+ message: `Your preferred price range is ${this.formatPriceRange(topPriceRange)}. Focus on properties in this range.`,
410
+ priority: 'medium'
411
+ });
412
+ }
413
+
414
+ // Recommendation 3: Based on engagement
415
+ if (insights.engagementLevel === 'high') {
416
+ recommendations.push({
417
+ type: 'engagement',
418
+ message: 'High engagement detected! You\'re ready for personalized recommendations and follow-up.',
419
+ priority: 'high'
420
+ });
421
+ } else if (insights.engagementLevel === 'medium') {
422
+ recommendations.push({
423
+ type: 'engagement',
424
+ message: 'Good engagement level. Consider scheduling property visits for interested properties.',
425
+ priority: 'medium'
426
+ });
427
+ }
428
+
429
+ // Recommendation 4: Based on session duration
430
+ if (insights.sessionDuration < 30) {
431
+ recommendations.push({
432
+ type: 'duration',
433
+ message: 'Short session duration. Consider providing more detailed property information.',
434
+ priority: 'low'
435
+ });
436
+ }
437
+
438
+ return recommendations;
439
+ }
440
+
441
+ displayInsights(insights) {
442
+ // Create insights section if it doesn't exist
443
+ let insightsSection = document.getElementById('insightsSection');
444
+ if (!insightsSection) {
445
+ insightsSection = document.createElement('div');
446
+ insightsSection.id = 'insightsSection';
447
+ insightsSection.className = 'row mb-4';
448
+ insightsSection.innerHTML = `
449
+ <div class="col-md-12">
450
+ <div class="chart-container">
451
+ <h5><i class="fas fa-lightbulb"></i> AI-Powered Insights & Recommendations</h5>
452
+ <div id="insightsContent"></div>
453
+ </div>
454
+ </div>
455
+ `;
456
+
457
+ // Insert after the charts row
458
+ const chartsRow = document.querySelector('.row.mb-4:nth-of-type(3)');
459
+ if (chartsRow) {
460
+ chartsRow.parentNode.insertBefore(insightsSection, chartsRow.nextSibling);
461
+ }
462
+ }
463
+
464
+ const insightsContent = document.getElementById('insightsContent');
465
+ let insightsHTML = '';
466
+
467
+ // Display engagement summary
468
+ insightsHTML += `
469
+ <div class="row mb-3">
470
+ <div class="col-md-6">
471
+ <div class="alert alert-info">
472
+ <h6><i class="fas fa-chart-line"></i> Engagement Summary</h6>
473
+ <p><strong>Level:</strong> <span class="badge bg-${insights.engagementLevel === 'high' ? 'success' : insights.engagementLevel === 'medium' ? 'warning' : 'secondary'}">${insights.engagementLevel.toUpperCase()}</span></p>
474
+ <p><strong>Session Duration:</strong> ${this.formatDuration(insights.sessionDuration)}</p>
475
+ <p><strong>Total Interactions:</strong> ${this.trackingData.clickedProperties.length + this.trackingData.detailedViews.length}</p>
476
+ </div>
477
+ </div>
478
+ <div class="col-md-6">
479
+ <div class="alert alert-success">
480
+ <h6><i class="fas fa-star"></i> Top Property Types</h6>
481
+ ${Object.entries(insights.preferredTypes)
482
+ .sort(([,a], [,b]) => b - a)
483
+ .slice(0, 3)
484
+ .map(([type, count]) => `<p><strong>${type}:</strong> ${count} interactions</p>`)
485
+ .join('')}
486
+ </div>
487
+ </div>
488
+ </div>
489
+ `;
490
+
491
+ // Display recommendations
492
+ insightsHTML += `
493
+ <div class="row">
494
+ <div class="col-md-12">
495
+ <h6><i class="fas fa-recommendations"></i> AI Recommendations</h6>
496
+ ${insights.recommendations.map(rec => `
497
+ <div class="alert alert-${rec.priority === 'high' ? 'danger' : rec.priority === 'medium' ? 'warning' : 'info'}">
498
+ <i class="fas fa-${rec.type === 'property_type' ? 'home' : rec.type === 'price_range' ? 'money-bill' : rec.type === 'engagement' ? 'chart-line' : 'clock'}"></i>
499
+ ${rec.message}
500
+ </div>
501
+ `).join('')}
502
+ </div>
503
+ </div>
504
+ `;
505
+
506
+ // Display top properties
507
+ if (insights.topProperties.length > 0) {
508
+ insightsHTML += `
509
+ <div class="row mt-3">
510
+ <div class="col-md-12">
511
+ <h6><i class="fas fa-trophy"></i> Most Engaged Properties</h6>
512
+ <div class="row">
513
+ ${insights.topProperties.map(prop => `
514
+ <div class="col-md-4">
515
+ <div class="card">
516
+ <div class="card-body">
517
+ <h6 class="card-title">${prop.propertyName.substring(0, 30)}...</h6>
518
+ <p class="card-text">
519
+ <strong>Type:</strong> ${prop.propertyType}<br>
520
+ <strong>Price:</strong> β‚Ή${this.formatPrice(prop.price)}<br>
521
+ <strong>Duration:</strong> ${this.formatDuration(prop.totalDuration)}<br>
522
+ <strong>Views:</strong> ${prop.viewCount}
523
+ </p>
524
+ </div>
525
+ </div>
526
+ </div>
527
+ `).join('')}
528
+ </div>
529
+ </div>
530
+ </div>
531
+ `;
532
+ }
533
+
534
+ insightsContent.innerHTML = insightsHTML;
535
+ }
536
+
537
+ createCharts() {
538
+ this.createPropertyTypeChart();
539
+ this.createPriceRangeChart();
540
+ this.createFeatureChart();
541
+ this.createViewDurationChart();
542
+ }
543
+
544
+ createPropertyTypeChart() {
545
+ const ctx = document.getElementById('propertyTypeChart').getContext('2d');
546
+ const typeCounts = {};
547
+
548
+ this.trackingData.propertyTypes.forEach(type => {
549
+ typeCounts[type] = (typeCounts[type] || 0) + 1;
550
+ });
551
+
552
+ if (Object.keys(typeCounts).length === 0) {
553
+ ctx.canvas.style.display = 'none';
554
+ return;
555
+ }
556
+
557
+ new Chart(ctx, {
558
+ type: 'doughnut',
559
+ data: {
560
+ labels: Object.keys(typeCounts),
561
+ datasets: [{
562
+ data: Object.values(typeCounts),
563
+ backgroundColor: [
564
+ '#FF6384',
565
+ '#36A2EB',
566
+ '#FFCE56',
567
+ '#4BC0C0',
568
+ '#9966FF',
569
+ '#FF9F40'
570
+ ]
571
+ }]
572
+ },
573
+ options: {
574
+ responsive: true,
575
+ plugins: {
576
+ legend: {
577
+ position: 'bottom'
578
+ }
579
+ }
580
+ }
581
+ });
582
+ }
583
+
584
+ createPriceRangeChart() {
585
+ const ctx = document.getElementById('priceRangeChart').getContext('2d');
586
+ const rangeCounts = {};
587
+
588
+ this.trackingData.priceRanges.forEach(range => {
589
+ rangeCounts[range] = (rangeCounts[range] || 0) + 1;
590
+ });
591
+
592
+ if (Object.keys(rangeCounts).length === 0) {
593
+ ctx.canvas.style.display = 'none';
594
+ return;
595
+ }
596
+
597
+ const labels = {
598
+ '0-500000': 'Under β‚Ή5L',
599
+ '500000-1000000': 'β‚Ή5L - β‚Ή10L',
600
+ '1000000-2000000': 'β‚Ή10L - β‚Ή20L',
601
+ '2000000-5000000': 'β‚Ή20L - β‚Ή50L',
602
+ '5000000-10000000': 'β‚Ή50L - β‚Ή1Cr',
603
+ '10000000+': 'Above β‚Ή1Cr'
604
+ };
605
+
606
+ new Chart(ctx, {
607
+ type: 'bar',
608
+ data: {
609
+ labels: Object.keys(rangeCounts).map(key => labels[key] || key),
610
+ datasets: [{
611
+ label: 'Clicks',
612
+ data: Object.values(rangeCounts),
613
+ backgroundColor: '#007bff'
614
+ }]
615
+ },
616
+ options: {
617
+ responsive: true,
618
+ scales: {
619
+ y: {
620
+ beginAtZero: true
621
+ }
622
+ }
623
+ }
624
+ });
625
+ }
626
+
627
+ createFeatureChart() {
628
+ const ctx = document.getElementById('featureChart').getContext('2d');
629
+ const featureCounts = {};
630
+
631
+ this.trackingData.features.forEach(feature => {
632
+ featureCounts[feature] = (featureCounts[feature] || 0) + 1;
633
+ });
634
+
635
+ if (Object.keys(featureCounts).length === 0) {
636
+ ctx.canvas.style.display = 'none';
637
+ return;
638
+ }
639
+
640
+ // Sort by count and take top 10
641
+ const sortedFeatures = Object.entries(featureCounts)
642
+ .sort(([,a], [,b]) => b - a)
643
+ .slice(0, 10);
644
+
645
+ new Chart(ctx, {
646
+ type: 'horizontalBar',
647
+ data: {
648
+ labels: sortedFeatures.map(([feature]) => feature),
649
+ datasets: [{
650
+ label: 'Clicks',
651
+ data: sortedFeatures.map(([,count]) => count),
652
+ backgroundColor: '#28a745'
653
+ }]
654
+ },
655
+ options: {
656
+ responsive: true,
657
+ scales: {
658
+ x: {
659
+ beginAtZero: true
660
+ }
661
+ }
662
+ }
663
+ });
664
+ }
665
+
666
+ createViewDurationChart() {
667
+ const ctx = document.getElementById('viewDurationChart').getContext('2d');
668
+ const detailedViews = this.trackingData.detailedViews;
669
+
670
+ if (detailedViews.length === 0) {
671
+ ctx.canvas.style.display = 'none';
672
+ return;
673
+ }
674
+
675
+ // Sort by total duration and take top 10
676
+ const sortedViews = detailedViews
677
+ .sort((a, b) => b.totalDuration - a.totalDuration)
678
+ .slice(0, 10);
679
+
680
+ new Chart(ctx, {
681
+ type: 'bar',
682
+ data: {
683
+ labels: sortedViews.map(view => view.propertyName.substring(0, 20) + '...'),
684
+ datasets: [{
685
+ label: 'Total Duration (seconds)',
686
+ data: sortedViews.map(view => view.totalDuration),
687
+ backgroundColor: '#ff6384'
688
+ }, {
689
+ label: 'View Count',
690
+ data: sortedViews.map(view => view.viewCount),
691
+ backgroundColor: '#36a2eb'
692
+ }]
693
+ },
694
+ options: {
695
+ responsive: true,
696
+ scales: {
697
+ y: {
698
+ beginAtZero: true
699
+ }
700
+ }
701
+ }
702
+ });
703
+ }
704
+
705
+ displayRecentActivity() {
706
+ const container = document.getElementById('activityList');
707
+ const allActivities = [];
708
+
709
+ // Add clicked properties
710
+ this.trackingData.clickedProperties.forEach(prop => {
711
+ allActivities.push({
712
+ type: 'click',
713
+ text: `Clicked on ${prop.name}`,
714
+ timestamp: new Date(prop.timestamp),
715
+ price: prop.price,
716
+ propertyType: prop.type
717
+ });
718
+ });
719
+
720
+ // Add detailed views
721
+ this.trackingData.detailedViews.forEach(view => {
722
+ allActivities.push({
723
+ type: 'detailed_view',
724
+ text: `Viewed ${view.propertyName} for ${this.formatDuration(view.totalDuration)}`,
725
+ timestamp: new Date(view.lastViewed),
726
+ price: view.price,
727
+ propertyType: view.propertyType,
728
+ viewCount: view.viewCount,
729
+ totalDuration: view.totalDuration
730
+ });
731
+ });
732
+
733
+ // Add searches
734
+ this.trackingData.searchHistory.forEach(search => {
735
+ allActivities.push({
736
+ type: 'search',
737
+ text: `Searched for "${search.query}"`,
738
+ timestamp: new Date(search.timestamp),
739
+ filters: search.filters
740
+ });
741
+ });
742
+
743
+ // Add visit requests
744
+ const visitHistory = JSON.parse(localStorage.getItem('visitHistory') || '[]');
745
+ visitHistory.forEach(visit => {
746
+ allActivities.push({
747
+ type: 'visit',
748
+ text: `Requested visit for ${visit.propertyName}`,
749
+ timestamp: new Date(visit.timestamp),
750
+ propertyType: visit.propertyType
751
+ });
752
+ });
753
+
754
+ // Sort by timestamp (most recent first)
755
+ allActivities.sort((a, b) => b.timestamp - a.timestamp);
756
+
757
+ if (allActivities.length === 0) {
758
+ container.innerHTML = '<p class="text-muted">No activity recorded yet.</p>';
759
+ return;
760
+ }
761
+
762
+ const activitiesHTML = allActivities.slice(0, 20).map(activity => {
763
+ let iconClass, icon;
764
+ if (activity.type === 'click') {
765
+ iconClass = 'click';
766
+ icon = 'fas fa-mouse-pointer';
767
+ } else if (activity.type === 'detailed_view') {
768
+ iconClass = 'visit';
769
+ icon = 'fas fa-clock';
770
+ } else if (activity.type === 'search') {
771
+ iconClass = 'search';
772
+ icon = 'fas fa-search';
773
+ } else {
774
+ iconClass = 'visit';
775
+ icon = 'fas fa-calendar';
776
+ }
777
+
778
+ return `
779
+ <div class="activity-item">
780
+ <div class="d-flex align-items-center">
781
+ <div class="activity-icon ${iconClass} me-3">
782
+ <i class="${icon}"></i>
783
+ </div>
784
+ <div>
785
+ <div class="fw-bold">${activity.text}</div>
786
+ <small class="text-muted">${activity.timestamp.toLocaleString()}</small>
787
+ ${activity.price ? `<br><small class="text-primary">β‚Ή${this.formatPrice(activity.price)}</small>` : ''}
788
+ ${activity.viewCount ? `<br><small class="text-success">Viewed ${activity.viewCount} times</small>` : ''}
789
+ </div>
790
+ </div>
791
+ </div>
792
+ `;
793
+ }).join('');
794
+
795
+ container.innerHTML = activitiesHTML;
796
+ }
797
+
798
+ updateLastUpdated() {
799
+ const now = new Date();
800
+ document.getElementById('lastUpdated').textContent = now.toLocaleString();
801
+ }
802
+
803
+ updateLeadScore() {
804
+ // Calculate lead score based on user behavior
805
+ let score = 0;
806
+
807
+ // Points for clicks
808
+ score += this.trackingData.clickedProperties.length * 5;
809
+
810
+ // Points for detailed views
811
+ score += this.trackingData.detailedViews.length * 10;
812
+
813
+ // Points for searches
814
+ score += this.trackingData.searchHistory.length * 3;
815
+
816
+ // Points for visit requests
817
+ const visitHistory = JSON.parse(localStorage.getItem('visitHistory') || '[]');
818
+ score += visitHistory.length * 15;
819
+
820
+ // Points for time spent
821
+ const totalViewTime = this.trackingData.detailedViews.reduce((sum, view) => sum + view.totalDuration, 0);
822
+ score += Math.min(totalViewTime / 60, 20); // Max 20 points for time
823
+
824
+ // Cap score at 100
825
+ score = Math.min(score, 100);
826
+
827
+ document.getElementById('leadScore').textContent = Math.round(score);
828
+
829
+ // Update lead status
830
+ const statusElement = document.getElementById('leadStatus');
831
+ statusElement.className = 'lead-status';
832
+
833
+ if (score >= 70) {
834
+ statusElement.textContent = 'Hot Lead';
835
+ statusElement.classList.add('hot');
836
+ } else if (score >= 40) {
837
+ statusElement.textContent = 'Warm Lead';
838
+ statusElement.classList.add('warm');
839
+ } else {
840
+ statusElement.textContent = 'Cold Lead';
841
+ statusElement.classList.add('cold');
842
+ }
843
+ }
844
+
845
+ formatPrice(price) {
846
+ if (price >= 10000000) {
847
+ return (price / 10000000).toFixed(1) + 'Cr';
848
+ } else if (price >= 100000) {
849
+ return (price / 100000).toFixed(1) + 'L';
850
+ } else {
851
+ return price.toLocaleString();
852
+ }
853
+ }
854
+
855
+ formatDuration(seconds) {
856
+ if (seconds < 60) {
857
+ return seconds + 's';
858
+ } else if (seconds < 3600) {
859
+ const minutes = Math.floor(seconds / 60);
860
+ const remainingSeconds = seconds % 60;
861
+ return minutes + 'm ' + remainingSeconds + 's';
862
+ } else {
863
+ const hours = Math.floor(seconds / 3600);
864
+ const minutes = Math.floor((seconds % 3600) / 60);
865
+ return hours + 'h ' + minutes + 'm';
866
+ }
867
+ }
868
+
869
+ formatPriceRange(range) {
870
+ const ranges = {
871
+ '0-500000': 'Under β‚Ή5L',
872
+ '500000-1000000': 'β‚Ή5L - β‚Ή10L',
873
+ '1000000-2000000': 'β‚Ή10L - β‚Ή20L',
874
+ '2000000-5000000': 'β‚Ή20L - β‚Ή50L',
875
+ '5000000-10000000': 'β‚Ή50L - β‚Ή1Cr',
876
+ '10000000+': 'Above β‚Ή1Cr'
877
+ };
878
+ return ranges[range] || range;
879
+ }
880
+ }
881
+
882
+ // Global functions for buttons
883
+ function clearData() {
884
+ if (confirm('Are you sure you want to clear all tracking data? This cannot be undone.')) {
885
+ localStorage.removeItem('userTrackingData');
886
+ localStorage.removeItem('userSessionId');
887
+ localStorage.removeItem('visitHistory');
888
+ location.reload();
889
+ }
890
+ }
891
+
892
+ function exportData() {
893
+ const trackingData = localStorage.getItem('userTrackingData');
894
+ const visitHistory = localStorage.getItem('visitHistory');
895
+
896
+ const exportData = {
897
+ tracking_data: trackingData ? JSON.parse(trackingData) : {},
898
+ visit_history: visitHistory ? JSON.parse(visitHistory) : [],
899
+ export_timestamp: new Date().toISOString()
900
+ };
901
+
902
+ const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
903
+ const url = URL.createObjectURL(blob);
904
+ const a = document.createElement('a');
905
+ a.href = url;
906
+ a.download = 'lead_analytics_data.json';
907
+ document.body.appendChild(a);
908
+ a.click();
909
+ document.body.removeChild(a);
910
+ URL.revokeObjectURL(url);
911
+ }
912
+
913
+ // Initialize dashboard
914
+ document.addEventListener('DOMContentLoaded', () => {
915
+ new LeadAnalyticsDashboard();
916
+ });
917
+ </script>
918
+ </body>
919
+ </html>
templates/enhanced_dashboard.html ADDED
@@ -0,0 +1,1264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Real Estate Lead Qualification Dashboard</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
9
+ <link href="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.min.css" rel="stylesheet">
10
+ <style>
11
+ :root {
12
+ --primary-color: #2c3e50;
13
+ --secondary-color: #3498db;
14
+ --success-color: #27ae60;
15
+ --warning-color: #f39c12;
16
+ --danger-color: #e74c3c;
17
+ --light-bg: #f8f9fa;
18
+ --dark-bg: #343a40;
19
+ }
20
+
21
+ body {
22
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
23
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
24
+ min-height: 100vh;
25
+ }
26
+
27
+ .dashboard-container {
28
+ background: rgba(255, 255, 255, 0.95);
29
+ border-radius: 20px;
30
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
31
+ margin: 20px;
32
+ overflow: hidden;
33
+ }
34
+
35
+ .header {
36
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
37
+ color: white;
38
+ padding: 30px;
39
+ text-align: center;
40
+ }
41
+
42
+ .header h1 {
43
+ margin: 0;
44
+ font-size: 2.5rem;
45
+ font-weight: 300;
46
+ }
47
+
48
+ .header p {
49
+ margin: 10px 0 0 0;
50
+ opacity: 0.9;
51
+ font-size: 1.1rem;
52
+ }
53
+
54
+ .search-section {
55
+ background: white;
56
+ padding: 30px;
57
+ border-bottom: 1px solid #eee;
58
+ }
59
+
60
+ .search-box {
61
+ background: var(--light-bg);
62
+ border: none;
63
+ border-radius: 50px;
64
+ padding: 15px 25px;
65
+ font-size: 1.1rem;
66
+ width: 100%;
67
+ max-width: 400px;
68
+ transition: all 0.3s ease;
69
+ }
70
+
71
+ .search-box:focus {
72
+ outline: none;
73
+ box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.3);
74
+ transform: translateY(-2px);
75
+ }
76
+
77
+ .btn-primary {
78
+ background: linear-gradient(135deg, var(--secondary-color), #2980b9);
79
+ border: none;
80
+ border-radius: 50px;
81
+ padding: 15px 30px;
82
+ font-weight: 600;
83
+ transition: all 0.3s ease;
84
+ }
85
+
86
+ .btn-primary:hover {
87
+ transform: translateY(-2px);
88
+ box-shadow: 0 10px 20px rgba(52, 152, 219, 0.3);
89
+ }
90
+
91
+ .content-section {
92
+ padding: 30px;
93
+ }
94
+
95
+ .stats-card {
96
+ background: white;
97
+ border-radius: 15px;
98
+ padding: 25px;
99
+ margin-bottom: 20px;
100
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
101
+ transition: all 0.3s ease;
102
+ border-left: 5px solid var(--secondary-color);
103
+ }
104
+
105
+ .stats-card:hover {
106
+ transform: translateY(-5px);
107
+ box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15);
108
+ }
109
+
110
+ .stats-card.success {
111
+ border-left-color: var(--success-color);
112
+ }
113
+
114
+ .stats-card.warning {
115
+ border-left-color: var(--warning-color);
116
+ }
117
+
118
+ .stats-card.danger {
119
+ border-left-color: var(--danger-color);
120
+ }
121
+
122
+ .stats-number {
123
+ font-size: 2.5rem;
124
+ font-weight: 700;
125
+ color: var(--primary-color);
126
+ margin-bottom: 10px;
127
+ }
128
+
129
+ .stats-label {
130
+ color: #666;
131
+ font-size: 0.9rem;
132
+ text-transform: uppercase;
133
+ letter-spacing: 1px;
134
+ }
135
+
136
+ .property-card {
137
+ background: white;
138
+ border-radius: 15px;
139
+ padding: 20px;
140
+ margin-bottom: 20px;
141
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
142
+ transition: all 0.3s ease;
143
+ border: 1px solid #eee;
144
+ }
145
+
146
+ .property-card:hover {
147
+ transform: translateY(-3px);
148
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
149
+ }
150
+
151
+ .property-name {
152
+ font-size: 1.2rem;
153
+ font-weight: 600;
154
+ color: var(--primary-color);
155
+ margin-bottom: 10px;
156
+ }
157
+
158
+ .property-details {
159
+ display: flex;
160
+ justify-content: space-between;
161
+ align-items: center;
162
+ flex-wrap: wrap;
163
+ gap: 10px;
164
+ }
165
+
166
+ .property-badge {
167
+ background: var(--light-bg);
168
+ color: var(--primary-color);
169
+ padding: 5px 12px;
170
+ border-radius: 20px;
171
+ font-size: 0.8rem;
172
+ font-weight: 600;
173
+ }
174
+
175
+ .price {
176
+ font-size: 1.3rem;
177
+ font-weight: 700;
178
+ color: var(--success-color);
179
+ }
180
+
181
+ .engagement-score {
182
+ display: inline-block;
183
+ padding: 5px 12px;
184
+ border-radius: 20px;
185
+ font-size: 0.8rem;
186
+ font-weight: 600;
187
+ color: white;
188
+ }
189
+
190
+ .engagement-high {
191
+ background: var(--success-color);
192
+ }
193
+
194
+ .engagement-medium {
195
+ background: var(--warning-color);
196
+ }
197
+
198
+ .engagement-low {
199
+ background: var(--danger-color);
200
+ }
201
+
202
+ .loading {
203
+ text-align: center;
204
+ padding: 50px;
205
+ color: #666;
206
+ }
207
+
208
+ .loading i {
209
+ font-size: 3rem;
210
+ color: var(--secondary-color);
211
+ animation: spin 1s linear infinite;
212
+ }
213
+
214
+ @keyframes spin {
215
+ 0% { transform: rotate(0deg); }
216
+ 100% { transform: rotate(360deg); }
217
+ }
218
+
219
+ .error-message {
220
+ background: #fee;
221
+ color: var(--danger-color);
222
+ padding: 20px;
223
+ border-radius: 10px;
224
+ border: 1px solid #fcc;
225
+ margin: 20px 0;
226
+ }
227
+
228
+ .chart-container {
229
+ background: white;
230
+ border-radius: 15px;
231
+ padding: 25px;
232
+ margin-bottom: 20px;
233
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
234
+ }
235
+
236
+ .analytics-section {
237
+ background: white;
238
+ border-radius: 15px;
239
+ padding: 25px;
240
+ margin-bottom: 20px;
241
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
242
+ }
243
+
244
+ .insight-item {
245
+ background: var(--light-bg);
246
+ padding: 15px;
247
+ border-radius: 10px;
248
+ margin-bottom: 10px;
249
+ border-left: 4px solid var(--secondary-color);
250
+ }
251
+
252
+ .insight-label {
253
+ font-weight: 600;
254
+ color: var(--primary-color);
255
+ margin-bottom: 5px;
256
+ }
257
+
258
+ .insight-value {
259
+ color: #666;
260
+ }
261
+
262
+ .recommendation {
263
+ background: #e8f5e8;
264
+ border: 1px solid #c3e6c3;
265
+ color: #2d5a2d;
266
+ padding: 15px;
267
+ border-radius: 10px;
268
+ margin-bottom: 10px;
269
+ }
270
+
271
+ .risk-high {
272
+ background: #fee;
273
+ border-color: #fcc;
274
+ color: #c33;
275
+ }
276
+
277
+ .risk-medium {
278
+ background: #fff3cd;
279
+ border-color: #ffeaa7;
280
+ color: #856404;
281
+ }
282
+
283
+ .risk-low {
284
+ background: #e8f5e8;
285
+ border-color: #c3e6c3;
286
+ color: #2d5a2d;
287
+ }
288
+
289
+ .no-data {
290
+ text-align: center;
291
+ padding: 50px;
292
+ color: #666;
293
+ }
294
+
295
+ .no-data i {
296
+ font-size: 4rem;
297
+ color: #ddd;
298
+ margin-bottom: 20px;
299
+ }
300
+
301
+ @media (max-width: 768px) {
302
+ .dashboard-container {
303
+ margin: 10px;
304
+ }
305
+
306
+ .header h1 {
307
+ font-size: 2rem;
308
+ }
309
+
310
+ .stats-number {
311
+ font-size: 2rem;
312
+ }
313
+
314
+ .property-details {
315
+ flex-direction: column;
316
+ align-items: flex-start;
317
+ }
318
+ }
319
+ </style>
320
+ </head>
321
+ <body>
322
+ <div class="dashboard-container">
323
+ <!-- Header -->
324
+ <div class="header">
325
+ <h1><i class="fas fa-chart-line"></i> Lead Qualification Dashboard</h1>
326
+ <p>Real-time property engagement analytics and insights</p>
327
+ </div>
328
+
329
+ <!-- Search Section -->
330
+ <div class="search-section">
331
+ <div class="row align-items-center">
332
+ <div class="col-md-6">
333
+ <div class="input-group">
334
+ <input type="number" id="customerIdInput" class="form-control search-box"
335
+ placeholder="Enter Customer ID (e.g., 105)" min="1">
336
+ <button class="btn btn-primary" onclick="loadCustomerData()">
337
+ <i class="fas fa-search"></i> Load Data
338
+ </button>
339
+ </div>
340
+ </div>
341
+ <div class="col-md-6 text-end">
342
+ <button class="btn btn-outline-secondary" onclick="loadSampleData()">
343
+ <i class="fas fa-eye"></i> View Sample Data
344
+ </button>
345
+ <button class="btn btn-outline-info" onclick="exportData()">
346
+ <i class="fas fa-download"></i> Export
347
+ </button>
348
+ </div>
349
+ </div>
350
+ </div>
351
+
352
+ <!-- Content Section -->
353
+ <div class="content-section">
354
+ <!-- Loading State -->
355
+ <div id="loadingState" class="loading" style="display: none;">
356
+ <i class="fas fa-spinner"></i>
357
+ <p>Loading customer data...</p>
358
+ </div>
359
+
360
+ <!-- Error State -->
361
+ <div id="errorState" class="error-message" style="display: none;">
362
+ <i class="fas fa-exclamation-triangle"></i>
363
+ <span id="errorMessage"></span>
364
+ </div>
365
+
366
+ <!-- No Data State -->
367
+ <div id="noDataState" class="no-data" style="display: none;">
368
+ <i class="fas fa-search"></i>
369
+ <h3>No Data Found</h3>
370
+ <p>Enter a customer ID to view their property engagement data</p>
371
+ </div>
372
+
373
+ <!-- Data Content -->
374
+ <div id="dataContent" style="display: none;">
375
+ <!-- Summary Statistics -->
376
+ <div class="row mb-4">
377
+ <div class="col-md-3">
378
+ <div class="stats-card">
379
+ <div class="stats-number" id="totalProperties">0</div>
380
+ <div class="stats-label">Total Properties</div>
381
+ </div>
382
+ </div>
383
+ <div class="col-md-3">
384
+ <div class="stats-card">
385
+ <div class="stats-number" id="totalViews">0</div>
386
+ <div class="stats-label">Total Views</div>
387
+ </div>
388
+ </div>
389
+ <div class="col-md-3">
390
+ <div class="stats-card">
391
+ <div class="stats-number" id="avgPrice">β‚Ή0</div>
392
+ <div class="stats-label">Average Price</div>
393
+ </div>
394
+ </div>
395
+ <div class="col-md-3">
396
+ <div class="stats-card">
397
+ <div class="stats-number" id="engagementScore">0</div>
398
+ <div class="stats-label">Engagement Score</div>
399
+ </div>
400
+ </div>
401
+ </div>
402
+
403
+ <!-- Charts Section -->
404
+ <div class="row mb-4">
405
+ <div class="col-md-6">
406
+ <div class="chart-container">
407
+ <h5><i class="fas fa-chart-pie"></i> Property Type Distribution</h5>
408
+ <canvas id="propertyTypeChart"></canvas>
409
+ </div>
410
+ </div>
411
+ <div class="col-md-6">
412
+ <div class="chart-container">
413
+ <h5><i class="fas fa-chart-bar"></i> Engagement by Property</h5>
414
+ <canvas id="engagementChart"></canvas>
415
+ </div>
416
+ </div>
417
+ </div>
418
+
419
+ <!-- Lead Qualification Section -->
420
+ <div class="row mb-4">
421
+ <div class="col-12">
422
+ <div class="analytics-section">
423
+ <h5><i class="fas fa-fire"></i> Lead Qualification Analysis</h5>
424
+ <div id="leadQualificationContainer"></div>
425
+ </div>
426
+ </div>
427
+ </div>
428
+
429
+ <!-- Detailed Analytics Section -->
430
+ <div class="row mb-4">
431
+ <div class="col-md-6">
432
+ <div class="analytics-section">
433
+ <h5><i class="fas fa-brain"></i> AI Insights</h5>
434
+ <div id="insightsContainer"></div>
435
+ </div>
436
+ </div>
437
+ <div class="col-md-6">
438
+ <div class="analytics-section">
439
+ <h5><i class="fas fa-lightbulb"></i> Recommendations</h5>
440
+ <div id="recommendationsContainer"></div>
441
+ </div>
442
+ </div>
443
+ </div>
444
+
445
+ <!-- Property Analysis Section -->
446
+ <div class="row mb-4">
447
+ <div class="col-12">
448
+ <div class="analytics-section">
449
+ <h5><i class="fas fa-home"></i> Detailed Property Analysis</h5>
450
+ <div id="propertyAnalysisContainer"></div>
451
+ </div>
452
+ </div>
453
+ </div>
454
+
455
+ <!-- Conversion Probability Section -->
456
+ <div class="row mb-4">
457
+ <div class="col-md-6">
458
+ <div class="analytics-section">
459
+ <h5><i class="fas fa-chart-line"></i> Conversion Probability</h5>
460
+ <div id="conversionContainer"></div>
461
+ </div>
462
+ </div>
463
+ <div class="col-md-6">
464
+ <div class="analytics-section">
465
+ <h5><i class="fas fa-clock"></i> Lead Timeline</h5>
466
+ <div id="timelineContainer"></div>
467
+ </div>
468
+ </div>
469
+ </div>
470
+
471
+ <!-- Properties List -->
472
+ <div class="row">
473
+ <div class="col-12">
474
+ <h5><i class="fas fa-home"></i> Property Engagement Details</h5>
475
+ <div id="propertiesContainer"></div>
476
+ </div>
477
+ </div>
478
+ </div>
479
+ </div>
480
+ </div>
481
+
482
+ <!-- Scripts -->
483
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
484
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"
485
+ onerror="console.error('Failed to load Chart.js from primary CDN')"></script>
486
+ <script>
487
+ // Fallback for Chart.js if primary CDN fails
488
+ if (typeof Chart === 'undefined') {
489
+ const script = document.createElement('script');
490
+ script.src = 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.js';
491
+ script.onload = function() {
492
+ console.log('Chart.js loaded from fallback CDN');
493
+ };
494
+ script.onerror = function() {
495
+ console.error('Failed to load Chart.js from fallback CDN');
496
+ };
497
+ document.head.appendChild(script);
498
+ }
499
+ </script>
500
+ <script>
501
+ let currentCustomerId = null;
502
+ let propertyTypeChart = null;
503
+ let engagementChart = null;
504
+
505
+ // Initialize dashboard
506
+ document.addEventListener('DOMContentLoaded', function() {
507
+ // Load sample data on page load
508
+ loadSampleData();
509
+
510
+ // Add enter key support for search
511
+ document.getElementById('customerIdInput').addEventListener('keypress', function(e) {
512
+ if (e.key === 'Enter') {
513
+ loadCustomerData();
514
+ }
515
+ });
516
+ });
517
+
518
+ function showLoading() {
519
+ document.getElementById('loadingState').style.display = 'block';
520
+ document.getElementById('errorState').style.display = 'none';
521
+ document.getElementById('noDataState').style.display = 'none';
522
+ document.getElementById('dataContent').style.display = 'none';
523
+ }
524
+
525
+ function showError(message) {
526
+ document.getElementById('loadingState').style.display = 'none';
527
+ document.getElementById('errorState').style.display = 'block';
528
+ document.getElementById('errorMessage').textContent = message;
529
+ document.getElementById('noDataState').style.display = 'none';
530
+ document.getElementById('dataContent').style.display = 'none';
531
+ }
532
+
533
+ function showNoData() {
534
+ document.getElementById('loadingState').style.display = 'none';
535
+ document.getElementById('errorState').style.display = 'none';
536
+ document.getElementById('noDataState').style.display = 'block';
537
+ document.getElementById('dataContent').style.display = 'none';
538
+ }
539
+
540
+ function showData() {
541
+ document.getElementById('loadingState').style.display = 'none';
542
+ document.getElementById('errorState').style.display = 'none';
543
+ document.getElementById('noDataState').style.display = 'none';
544
+ document.getElementById('dataContent').style.display = 'block';
545
+ }
546
+
547
+ function formatPrice(price) {
548
+ return new Intl.NumberFormat('en-IN', {
549
+ style: 'currency',
550
+ currency: 'INR',
551
+ maximumFractionDigits: 0
552
+ }).format(price);
553
+ }
554
+
555
+ function formatDuration(seconds) {
556
+ const hours = Math.floor(seconds / 3600);
557
+ const minutes = Math.floor((seconds % 3600) / 60);
558
+ return `${hours}h ${minutes}m`;
559
+ }
560
+
561
+ function getEngagementClass(score) {
562
+ if (score >= 80) return 'engagement-high';
563
+ if (score >= 50) return 'engagement-medium';
564
+ return 'engagement-low';
565
+ }
566
+
567
+ function getRiskClass(level) {
568
+ switch(level.toLowerCase()) {
569
+ case 'high': return 'risk-high';
570
+ case 'medium': return 'risk-medium';
571
+ case 'low': return 'risk-low';
572
+ default: return '';
573
+ }
574
+ }
575
+
576
+ async function loadCustomerData() {
577
+ const customerId = document.getElementById('customerIdInput').value.trim();
578
+
579
+ if (!customerId) {
580
+ showError('Please enter a customer ID');
581
+ return;
582
+ }
583
+
584
+ showLoading();
585
+ currentCustomerId = customerId;
586
+
587
+ try {
588
+ console.log('πŸ” Making combined API request for customer:', customerId);
589
+ console.log('πŸ“‘ Request URL:', `/api/lead-analysis/${customerId}`);
590
+
591
+ // Load combined lead analysis data
592
+ const response = await fetch(`/api/lead-analysis/${customerId}`);
593
+ console.log('πŸ“₯ Response status:', response.status);
594
+ console.log('πŸ“₯ Response headers:', Object.fromEntries(response.headers.entries()));
595
+
596
+ if (!response.ok) {
597
+ throw new Error(`HTTP error! status: ${response.status}`);
598
+ }
599
+
600
+ const combinedData = await response.json();
601
+ console.log('πŸ“Š Combined Lead Analysis Data:', combinedData);
602
+
603
+ if (combinedData.error) {
604
+ showError(combinedData.error);
605
+ return;
606
+ }
607
+
608
+ if (!combinedData.properties || combinedData.properties.length === 0) {
609
+ showNoData();
610
+ return;
611
+ }
612
+
613
+ // Display network info
614
+ displayNetworkInfo(customerId, combinedData);
615
+
616
+ // Display all data from single response
617
+ displayData(combinedData);
618
+
619
+ } catch (error) {
620
+ console.error('❌ Error loading data:', error);
621
+ showError(`Failed to load data: ${error.message}`);
622
+ }
623
+ }
624
+
625
+ function loadSampleData() {
626
+ document.getElementById('customerIdInput').value = '105';
627
+ loadCustomerData();
628
+ }
629
+
630
+ function displayData(data) {
631
+ showData();
632
+
633
+ // Update summary statistics
634
+ const summary = data.summary;
635
+ document.getElementById('totalProperties').textContent = summary.total_properties;
636
+ document.getElementById('totalViews').textContent = summary.total_views;
637
+ document.getElementById('avgPrice').textContent = formatPrice(summary.average_price);
638
+ document.getElementById('engagementScore').textContent = Math.round(summary.engagement_score);
639
+
640
+ // Display all sections immediately
641
+ displayLeadQualification(data);
642
+ displayInsights(data);
643
+ displayRecommendations(data);
644
+ displayPropertyAnalysis(data);
645
+ displayConversionProbability(data);
646
+ displayTimeline(data);
647
+ displayProperties(data.properties);
648
+
649
+ // Create charts with delay to ensure Chart.js is loaded
650
+ setTimeout(() => {
651
+ createPropertyTypeChart(summary.property_types);
652
+ createEngagementChart(data.properties);
653
+ }, 500);
654
+ }
655
+
656
+ function displayLeadQualification(data) {
657
+ const container = document.getElementById('leadQualificationContainer');
658
+ container.innerHTML = '';
659
+
660
+ if (!data || !data.lead_qualification) {
661
+ container.innerHTML = '<p class="text-muted">No lead qualification data available</p>';
662
+ return;
663
+ }
664
+
665
+ const lead = data.lead_qualification;
666
+
667
+ // Lead status card
668
+ const statusCard = document.createElement('div');
669
+ statusCard.className = 'row mb-3';
670
+ statusCard.innerHTML = `
671
+ <div class="col-md-6">
672
+ <div class="stats-card" style="border-left-color: ${lead.status_color}">
673
+ <div class="stats-number" style="color: ${lead.status_color}">${lead.lead_status}</div>
674
+ <div class="stats-label">Lead Status</div>
675
+ <small class="text-muted">${lead.status_description}</small>
676
+ </div>
677
+ </div>
678
+ <div class="col-md-6">
679
+ <div class="stats-card">
680
+ <div class="stats-number">${lead.lead_score}/100</div>
681
+ <div class="stats-label">Lead Score</div>
682
+ <small class="text-muted">Based on multiple factors</small>
683
+ </div>
684
+ </div>
685
+ `;
686
+ container.appendChild(statusCard);
687
+
688
+ // Factors breakdown
689
+ const factorsCard = document.createElement('div');
690
+ factorsCard.className = 'row';
691
+ factorsCard.innerHTML = '<div class="col-12"><h6>Lead Score Breakdown:</h6></div>';
692
+
693
+ Object.entries(lead.factors).forEach(([factor, data]) => {
694
+ const factorDiv = document.createElement('div');
695
+ factorDiv.className = 'col-md-6 mb-2';
696
+ factorDiv.innerHTML = `
697
+ <div class="insight-item">
698
+ <div class="d-flex justify-content-between align-items-center">
699
+ <div>
700
+ <div class="insight-label">${factor.replace('_', ' ').toUpperCase()}</div>
701
+ <div class="insight-value">${data.description}</div>
702
+ </div>
703
+ <div class="text-end">
704
+ <div class="fw-bold">${data.points.toFixed(1)}/${data.max_points}</div>
705
+ <small class="text-muted">Score: ${data.score}</small>
706
+ </div>
707
+ </div>
708
+ </div>
709
+ `;
710
+ factorsCard.appendChild(factorDiv);
711
+ });
712
+
713
+ container.appendChild(factorsCard);
714
+ }
715
+
716
+ function displayPropertyAnalysis(data) {
717
+ const container = document.getElementById('propertyAnalysisContainer');
718
+ container.innerHTML = '';
719
+
720
+ if (!data || !data.analytics || !data.analytics.property_analysis) {
721
+ container.innerHTML = '<p class="text-muted">No property analysis available</p>';
722
+ return;
723
+ }
724
+
725
+ const analysis = data.analytics.property_analysis;
726
+
727
+ // Price Analysis
728
+ if (analysis.price_analysis && analysis.price_analysis.min_price !== undefined) {
729
+ const priceDiv = document.createElement('div');
730
+ priceDiv.className = 'mb-3';
731
+ priceDiv.innerHTML = `
732
+ <h6><i class="fas fa-money-bill-wave"></i> Price Analysis</h6>
733
+ <div class="row">
734
+ <div class="col-md-3">
735
+ <div class="insight-item">
736
+ <div class="insight-label">Price Range</div>
737
+ <div class="insight-value">${formatPrice(analysis.price_analysis.min_price)} - ${formatPrice(analysis.price_analysis.max_price)}</div>
738
+ </div>
739
+ </div>
740
+ <div class="col-md-3">
741
+ <div class="insight-item">
742
+ <div class="insight-label">Average Price</div>
743
+ <div class="insight-value">${formatPrice(analysis.price_analysis.avg_price)}</div>
744
+ </div>
745
+ </div>
746
+ <div class="col-md-3">
747
+ <div class="insight-item">
748
+ <div class="insight-label">Preferred Range</div>
749
+ <div class="insight-value">${getPreferredRangeDisplay(analysis.price_analysis.preferred_range)}</div>
750
+ </div>
751
+ </div>
752
+ <div class="col-md-3">
753
+ <div class="insight-item">
754
+ <div class="insight-label">Price Trend</div>
755
+ <div class="insight-value">${analysis.price_analysis.price_trend || 'N/A'}</div>
756
+ </div>
757
+ </div>
758
+ </div>
759
+ `;
760
+ container.appendChild(priceDiv);
761
+ }
762
+
763
+ // Duration Analysis
764
+ if (analysis.duration_analysis && analysis.duration_analysis.total_duration !== undefined) {
765
+ const durationDiv = document.createElement('div');
766
+ durationDiv.className = 'mb-3';
767
+ durationDiv.innerHTML = `
768
+ <h6><i class="fas fa-clock"></i> Duration Analysis</h6>
769
+ <div class="row">
770
+ <div class="col-md-3">
771
+ <div class="insight-item">
772
+ <div class="insight-label">Total Duration</div>
773
+ <div class="insight-value">${formatDuration(analysis.duration_analysis.total_duration)}</div>
774
+ </div>
775
+ </div>
776
+ <div class="col-md-3">
777
+ <div class="insight-item">
778
+ <div class="insight-label">Average Duration</div>
779
+ <div class="insight-value">${formatDuration(analysis.duration_analysis.avg_duration)}</div>
780
+ </div>
781
+ </div>
782
+ <div class="col-md-3">
783
+ <div class="insight-item">
784
+ <div class="insight-label">Max Duration</div>
785
+ <div class="insight-value">${formatDuration(analysis.duration_analysis.max_duration)}</div>
786
+ </div>
787
+ </div>
788
+ <div class="col-md-3">
789
+ <div class="insight-item">
790
+ <div class="insight-label">Min Duration</div>
791
+ <div class="insight-value">${formatDuration(analysis.duration_analysis.min_duration)}</div>
792
+ </div>
793
+ </div>
794
+ </div>
795
+ `;
796
+ container.appendChild(durationDiv);
797
+ }
798
+
799
+ // Individual Property Analysis
800
+ if (analysis.properties && Array.isArray(analysis.properties) && analysis.properties.length > 0) {
801
+ const propertiesDiv = document.createElement('div');
802
+ propertiesDiv.className = 'mt-4';
803
+ propertiesDiv.innerHTML = '<h6><i class="fas fa-list"></i> Individual Property Analysis</h6>';
804
+
805
+ analysis.properties.forEach(prop => {
806
+ if (!prop || typeof prop !== 'object') return;
807
+
808
+ const propDiv = document.createElement('div');
809
+ propDiv.className = 'property-card mb-3';
810
+ propDiv.innerHTML = `
811
+ <div class="row">
812
+ <div class="col-md-8">
813
+ <div class="property-name">${prop.property_name || 'Unknown Property'}</div>
814
+ <div class="property-details">
815
+ <span class="property-badge">${prop.property_type || 'Unknown'}</span>
816
+ <span class="property-badge">${prop.view_count || 0} views</span>
817
+ <span class="property-badge">${prop.duration_formatted || '0h 0m'}</span>
818
+ <span class="property-badge ${getInterestClass(prop.interest_level || 'Unknown')}">${prop.interest_level || 'Unknown'} Interest</span>
819
+ </div>
820
+ </div>
821
+ <div class="col-md-4 text-end">
822
+ <div class="price">${prop.price_formatted || 'β‚Ή0'}</div>
823
+ <div class="engagement-score ${getEngagementClass(prop.engagement_score || 0)}">${Math.round(prop.engagement_score || 0)}%</div>
824
+ </div>
825
+ </div>
826
+ <div class="mt-2">
827
+ <small class="text-muted">
828
+ <i class="fas fa-lightbulb"></i> ${prop.recommendation || 'No recommendation available'}
829
+ </small>
830
+ </div>
831
+ `;
832
+ propertiesDiv.appendChild(propDiv);
833
+ });
834
+
835
+ container.appendChild(propertiesDiv);
836
+ }
837
+ }
838
+
839
+ function displayConversionProbability(data) {
840
+ const container = document.getElementById('conversionContainer');
841
+ container.innerHTML = '';
842
+
843
+ if (!data || !data.analytics || !data.analytics.conversion_probability) {
844
+ container.innerHTML = '<p class="text-muted">No conversion data available</p>';
845
+ return;
846
+ }
847
+
848
+ const conversion = data.analytics.conversion_probability;
849
+
850
+ // Main probability card
851
+ const probCard = document.createElement('div');
852
+ probCard.className = 'stats-card mb-3';
853
+ probCard.innerHTML = `
854
+ <div class="stats-number">${Math.round(conversion.final_probability)}%</div>
855
+ <div class="stats-label">Conversion Probability</div>
856
+ <small class="text-muted">Confidence: ${conversion.confidence_level}</small>
857
+ `;
858
+ container.appendChild(probCard);
859
+
860
+ // Breakdown
861
+ const breakdownDiv = document.createElement('div');
862
+ breakdownDiv.innerHTML = `
863
+ <h6>Probability Breakdown:</h6>
864
+ <div class="insight-item">
865
+ <div class="d-flex justify-content-between">
866
+ <span>Base Probability:</span>
867
+ <span>${Math.round(conversion.base_probability)}%</span>
868
+ </div>
869
+ </div>
870
+ `;
871
+
872
+ Object.entries(conversion.adjustments).forEach(([factor, value]) => {
873
+ const factorDiv = document.createElement('div');
874
+ factorDiv.className = 'insight-item';
875
+ factorDiv.innerHTML = `
876
+ <div class="d-flex justify-content-between">
877
+ <span>${factor.replace('_', ' ').toUpperCase()}:</span>
878
+ <span>+${Math.round(value)}%</span>
879
+ </div>
880
+ `;
881
+ breakdownDiv.appendChild(factorDiv);
882
+ });
883
+
884
+ container.appendChild(breakdownDiv);
885
+ }
886
+
887
+ function displayTimeline(data) {
888
+ const container = document.getElementById('timelineContainer');
889
+ container.innerHTML = '';
890
+
891
+ if (!data || !data.analytics || !data.analytics.lead_timeline || data.analytics.lead_timeline.length === 0) {
892
+ container.innerHTML = '<p class="text-muted">No timeline data available</p>';
893
+ return;
894
+ }
895
+
896
+ const timeline = data.analytics.lead_timeline;
897
+
898
+ timeline.forEach((event, index) => {
899
+ const eventDiv = document.createElement('div');
900
+ eventDiv.className = 'insight-item mb-2';
901
+ eventDiv.innerHTML = `
902
+ <div class="d-flex justify-content-between align-items-start">
903
+ <div>
904
+ <div class="insight-label">${event.property_name}</div>
905
+ <div class="insight-value">
906
+ ${event.property_type} β€’ ${formatPrice(event.price)} β€’ ${event.views} views
907
+ </div>
908
+ <small class="text-muted">${formatDate(event.date)}</small>
909
+ </div>
910
+ <div class="text-end">
911
+ <div class="engagement-score ${getEngagementClass(event.engagement_score)}">${Math.round(event.engagement_score)}%</div>
912
+ </div>
913
+ </div>
914
+ `;
915
+ container.appendChild(eventDiv);
916
+ });
917
+ }
918
+
919
+ function createPropertyTypeChart(propertyTypes) {
920
+ try {
921
+ const canvas = document.getElementById('propertyTypeChart');
922
+ if (!canvas) {
923
+ console.error('Property type chart canvas not found');
924
+ return;
925
+ }
926
+
927
+ const ctx = canvas.getContext('2d');
928
+
929
+ if (propertyTypeChart) {
930
+ propertyTypeChart.destroy();
931
+ }
932
+
933
+ const labels = Object.keys(propertyTypes);
934
+ const data = Object.values(propertyTypes);
935
+
936
+ if (typeof Chart === 'undefined') {
937
+ console.error('Chart.js not loaded');
938
+ return;
939
+ }
940
+
941
+ propertyTypeChart = new Chart(ctx, {
942
+ type: 'doughnut',
943
+ data: {
944
+ labels: labels,
945
+ datasets: [{
946
+ data: data,
947
+ backgroundColor: [
948
+ '#3498db',
949
+ '#e74c3c',
950
+ '#2ecc71',
951
+ '#f39c12',
952
+ '#9b59b6',
953
+ '#1abc9c'
954
+ ]
955
+ }]
956
+ },
957
+ options: {
958
+ responsive: true,
959
+ plugins: {
960
+ legend: {
961
+ position: 'bottom'
962
+ }
963
+ }
964
+ }
965
+ });
966
+ } catch (error) {
967
+ console.error('Error creating property type chart:', error);
968
+ }
969
+ }
970
+
971
+ function createEngagementChart(properties) {
972
+ try {
973
+ const canvas = document.getElementById('engagementChart');
974
+ if (!canvas) {
975
+ console.error('Engagement chart canvas not found');
976
+ return;
977
+ }
978
+
979
+ const ctx = canvas.getContext('2d');
980
+
981
+ if (engagementChart) {
982
+ engagementChart.destroy();
983
+ }
984
+
985
+ const labels = properties.map(p => p.propertyName.substring(0, 20) + '...');
986
+ const data = properties.map(p => p.engagement_score || 0);
987
+
988
+ if (typeof Chart === 'undefined') {
989
+ console.error('Chart.js not loaded');
990
+ return;
991
+ }
992
+
993
+ engagementChart = new Chart(ctx, {
994
+ type: 'bar',
995
+ data: {
996
+ labels: labels,
997
+ datasets: [{
998
+ label: 'Engagement Score',
999
+ data: data,
1000
+ backgroundColor: '#3498db',
1001
+ borderColor: '#2980b9',
1002
+ borderWidth: 1
1003
+ }]
1004
+ },
1005
+ options: {
1006
+ responsive: true,
1007
+ scales: {
1008
+ y: {
1009
+ beginAtZero: true,
1010
+ max: 100
1011
+ }
1012
+ },
1013
+ plugins: {
1014
+ legend: {
1015
+ display: false
1016
+ }
1017
+ }
1018
+ }
1019
+ });
1020
+ } catch (error) {
1021
+ console.error('Error creating engagement chart:', error);
1022
+ }
1023
+ }
1024
+
1025
+ function displayInsights(data) {
1026
+ const container = document.getElementById('insightsContainer');
1027
+ container.innerHTML = '';
1028
+
1029
+ if (!data || !data.analytics || Object.keys(data.analytics).length === 0) {
1030
+ container.innerHTML = '<p class="text-muted">No insights available</p>';
1031
+ return;
1032
+ }
1033
+
1034
+ const analytics = data.analytics;
1035
+ const insights = [
1036
+ { label: 'Engagement Level', value: analytics.engagement_level || 'Unknown' },
1037
+ { label: 'Preferred Types', value: (analytics.preferred_property_types || []).join(', ') || 'None' },
1038
+ { label: 'Opportunity Score', value: `${Math.round(analytics.opportunity_score || 0)}%` },
1039
+ { label: 'Risk Level', value: analytics.risk_assessment?.risk_level || 'Unknown' }
1040
+ ];
1041
+
1042
+ insights.forEach(insight => {
1043
+ const div = document.createElement('div');
1044
+ div.className = 'insight-item';
1045
+ div.innerHTML = `
1046
+ <div class="insight-label">${insight.label}</div>
1047
+ <div class="insight-value">${insight.value}</div>
1048
+ `;
1049
+ container.appendChild(div);
1050
+ });
1051
+ }
1052
+
1053
+ function displayRecommendations(data) {
1054
+ const container = document.getElementById('recommendationsContainer');
1055
+ container.innerHTML = '';
1056
+
1057
+ if (!data || !data.analytics || !data.analytics.recommendations || data.analytics.recommendations.length === 0) {
1058
+ container.innerHTML = '<p class="text-muted">No recommendations available</p>';
1059
+ return;
1060
+ }
1061
+
1062
+ data.analytics.recommendations.forEach(rec => {
1063
+ const div = document.createElement('div');
1064
+ div.className = 'recommendation';
1065
+ div.innerHTML = `<i class="fas fa-lightbulb"></i> ${rec}`;
1066
+ container.appendChild(div);
1067
+ });
1068
+
1069
+ // Add risk assessment if available
1070
+ if (data.analytics.risk_assessment && data.analytics.risk_assessment.risk_factors) {
1071
+ const riskDiv = document.createElement('div');
1072
+ riskDiv.className = `recommendation ${getRiskClass(data.analytics.risk_assessment.risk_level)}`;
1073
+ riskDiv.innerHTML = `
1074
+ <strong><i class="fas fa-exclamation-triangle"></i> Risk Factors:</strong><br>
1075
+ ${data.analytics.risk_assessment.risk_factors.join(', ')}
1076
+ `;
1077
+ container.appendChild(riskDiv);
1078
+ }
1079
+ }
1080
+
1081
+ function displayProperties(properties) {
1082
+ const container = document.getElementById('propertiesContainer');
1083
+ container.innerHTML = '';
1084
+
1085
+ properties.forEach(property => {
1086
+ const div = document.createElement('div');
1087
+ div.className = 'property-card';
1088
+
1089
+ const engagementClass = getEngagementClass(property.engagement_score || 0);
1090
+ const engagementText = property.engagement_score ? `${Math.round(property.engagement_score)}%` : 'N/A';
1091
+
1092
+ div.innerHTML = `
1093
+ <div class="property-name">${property.propertyName}</div>
1094
+ <div class="property-details">
1095
+ <div>
1096
+ <span class="property-badge">${property.propertyTypeName}</span>
1097
+ <span class="property-badge">${property.viewCount} views</span>
1098
+ <span class="property-badge">${formatDuration(property.totalDuration)}</span>
1099
+ </div>
1100
+ <div>
1101
+ <span class="price">${formatPrice(property.price)}</span>
1102
+ <span class="engagement-score ${engagementClass}">${engagementText}</span>
1103
+ </div>
1104
+ </div>
1105
+ <div class="mt-2 text-muted">
1106
+ <small>
1107
+ <i class="fas fa-clock"></i> Last viewed: ${formatDate(property.lastViewedAt)}
1108
+ ${property.last_viewed_days_ago !== undefined ? `(${property.last_viewed_days_ago} days ago)` : ''}
1109
+ </small>
1110
+ </div>
1111
+ `;
1112
+
1113
+ container.appendChild(div);
1114
+ });
1115
+ }
1116
+
1117
+ function formatDate(dateString) {
1118
+ if (!dateString) return 'Unknown';
1119
+
1120
+ try {
1121
+ const date = new Date(dateString);
1122
+ return date.toLocaleDateString('en-IN', {
1123
+ year: 'numeric',
1124
+ month: 'short',
1125
+ day: 'numeric',
1126
+ hour: '2-digit',
1127
+ minute: '2-digit'
1128
+ });
1129
+ } catch (e) {
1130
+ return 'Invalid date';
1131
+ }
1132
+ }
1133
+
1134
+ async function exportData() {
1135
+ if (!currentCustomerId) {
1136
+ alert('Please load customer data first');
1137
+ return;
1138
+ }
1139
+
1140
+ try {
1141
+ const response = await fetch(`/api/export/${currentCustomerId}?format=json`);
1142
+ const data = await response.json();
1143
+
1144
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
1145
+ const url = window.URL.createObjectURL(blob);
1146
+ const a = document.createElement('a');
1147
+ a.href = url;
1148
+ a.download = `customer_${currentCustomerId}_data.json`;
1149
+ document.body.appendChild(a);
1150
+ a.click();
1151
+ document.body.removeChild(a);
1152
+ window.URL.revokeObjectURL(url);
1153
+
1154
+ } catch (error) {
1155
+ console.error('Export error:', error);
1156
+ alert('Failed to export data');
1157
+ }
1158
+ }
1159
+
1160
+ function getInterestClass(level) {
1161
+ switch(level.toLowerCase()) {
1162
+ case 'very high': return 'engagement-high';
1163
+ case 'high': return 'engagement-medium';
1164
+ case 'medium': return 'engagement-medium';
1165
+ case 'low': return 'engagement-low';
1166
+ case 'very low': return 'engagement-low';
1167
+ default: return '';
1168
+ }
1169
+ }
1170
+
1171
+ function getPreferredRangeDisplay(preferredRange) {
1172
+ if (!preferredRange || typeof preferredRange !== 'string') {
1173
+ return 'N/A';
1174
+ }
1175
+ return preferredRange.replace('_', ' ').toUpperCase();
1176
+ }
1177
+
1178
+ function displayNetworkInfo(customerId, data) {
1179
+ // Create network info section
1180
+ const networkInfo = `
1181
+ <div class="analytics-section mb-4">
1182
+ <h5><i class="fas fa-network-wired"></i> Network Request Information</h5>
1183
+ <div class="row">
1184
+ <div class="col-md-6">
1185
+ <h6>API Requests Made:</h6>
1186
+ <div class="insight-item">
1187
+ <div class="insight-label">Lead Analysis Request</div>
1188
+ <div class="insight-value">GET /api/lead-analysis/${customerId}</div>
1189
+ <small class="text-muted">Status: 200 OK</small>
1190
+ </div>
1191
+ </div>
1192
+ <div class="col-md-6">
1193
+ <h6>Data Summary:</h6>
1194
+ <div class="insight-item">
1195
+ <div class="insight-label">Properties Found</div>
1196
+ <div class="insight-value">${data.properties ? data.properties.length : 0}</div>
1197
+ </div>
1198
+ <div class="insight-item">
1199
+ <div class="insight-label">Analytics Generated</div>
1200
+ <div class="insight-value">${Object.keys(data).length} sections</div>
1201
+ </div>
1202
+ </div>
1203
+ </div>
1204
+
1205
+ <div class="mt-3">
1206
+ <button class="btn btn-sm btn-outline-info" onclick="showRawData()">
1207
+ <i class="fas fa-code"></i> View Raw JSON Data
1208
+ </button>
1209
+ <button class="btn btn-sm btn-outline-secondary" onclick="copyToClipboard()">
1210
+ <i class="fas fa-copy"></i> Copy to Clipboard
1211
+ </button>
1212
+ </div>
1213
+
1214
+ <div id="rawDataContainer" style="display: none;" class="mt-3">
1215
+ <h6>Raw API Response Data:</h6>
1216
+ <div class="row">
1217
+ <div class="col-md-6">
1218
+ <h6>Combined Lead Analysis Data:</h6>
1219
+ <pre class="bg-light p-2 rounded" style="max-height: 300px; overflow-y: auto; font-size: 0.8rem;">${JSON.stringify(data, null, 2)}</pre>
1220
+ </div>
1221
+ </div>
1222
+ </div>
1223
+ </div>
1224
+ `;
1225
+
1226
+ // Insert network info at the top of the data content
1227
+ const dataContent = document.getElementById('dataContent');
1228
+ dataContent.insertAdjacentHTML('afterbegin', networkInfo);
1229
+ }
1230
+
1231
+ function showRawData() {
1232
+ const container = document.getElementById('rawDataContainer');
1233
+ if (container.style.display === 'none') {
1234
+ container.style.display = 'block';
1235
+ } else {
1236
+ container.style.display = 'none';
1237
+ }
1238
+ }
1239
+
1240
+ function copyToClipboard() {
1241
+ const customerId = document.getElementById('customerIdInput').value.trim();
1242
+ const data = {
1243
+ customer_id: customerId,
1244
+ timestamp: new Date().toISOString(),
1245
+ api_requests: [
1246
+ {
1247
+ url: `/api/lead-analysis/${customerId}`,
1248
+ method: 'GET',
1249
+ description: 'Combined lead analysis data'
1250
+ }
1251
+ ],
1252
+ note: 'Check browser Network tab for actual request/response details'
1253
+ };
1254
+
1255
+ navigator.clipboard.writeText(JSON.stringify(data, null, 2)).then(() => {
1256
+ alert('Network information copied to clipboard!');
1257
+ }).catch(() => {
1258
+ alert('Failed to copy to clipboard. Check console for data.');
1259
+ console.log('Network Info:', data);
1260
+ });
1261
+ }
1262
+ </script>
1263
+ </body>
1264
+ </html>
templates/index.html ADDED
@@ -0,0 +1,2272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Real Estate Lead Qualification Engine</title>
7
+
8
+ <!-- Bootstrap CSS -->
9
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
10
+
11
+ <!-- Font Awesome -->
12
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
13
+
14
+ <!-- Google Fonts -->
15
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
16
+
17
+ <!-- Custom CSS -->
18
+ <style>
19
+ :root {
20
+ --primary-color: #2563eb;
21
+ --secondary-color: #64748b;
22
+ --success-color: #10b981;
23
+ --warning-color: #f59e0b;
24
+ --danger-color: #ef4444;
25
+ --dark-color: #1e293b;
26
+ --light-color: #f8fafc;
27
+ --border-color: #e2e8f0;
28
+ }
29
+
30
+ * {
31
+ margin: 0;
32
+ padding: 0;
33
+ box-sizing: border-box;
34
+ }
35
+
36
+ body {
37
+ font-family: 'Inter', sans-serif;
38
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
39
+ min-height: 100vh;
40
+ color: var(--dark-color);
41
+ }
42
+
43
+ .navbar {
44
+ background: rgba(255, 255, 255, 0.95) !important;
45
+ backdrop-filter: blur(10px);
46
+ border-bottom: 1px solid var(--border-color);
47
+ }
48
+
49
+ .hero-section {
50
+ background: linear-gradient(135deg, rgba(37, 99, 235, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
51
+ padding: 80px 0;
52
+ text-align: center;
53
+ }
54
+
55
+ .hero-title {
56
+ font-size: 3.5rem;
57
+ font-weight: 700;
58
+ color: var(--dark-color);
59
+ margin-bottom: 1rem;
60
+ }
61
+
62
+ .hero-subtitle {
63
+ font-size: 1.25rem;
64
+ color: var(--secondary-color);
65
+ margin-bottom: 2rem;
66
+ }
67
+
68
+ .search-container {
69
+ background: white;
70
+ border-radius: 20px;
71
+ padding: 2rem;
72
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
73
+ margin-bottom: 3rem;
74
+ }
75
+
76
+ .property-grid {
77
+ display: grid;
78
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
79
+ gap: 2rem;
80
+ margin-top: 2rem;
81
+ }
82
+
83
+ .property-card {
84
+ background: white;
85
+ border-radius: 15px;
86
+ overflow: hidden;
87
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
88
+ transition: all 0.3s ease;
89
+ cursor: pointer;
90
+ position: relative;
91
+ }
92
+
93
+ .property-card:hover {
94
+ transform: translateY(-5px);
95
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
96
+ }
97
+
98
+ .property-image {
99
+ width: 100%;
100
+ height: 250px;
101
+ object-fit: cover;
102
+ transition: transform 0.3s ease;
103
+ }
104
+
105
+ .property-card:hover .property-image {
106
+ transform: scale(1.05);
107
+ }
108
+
109
+ .property-badge {
110
+ position: absolute;
111
+ top: 15px;
112
+ right: 15px;
113
+ background: var(--primary-color);
114
+ color: white;
115
+ padding: 0.5rem 1rem;
116
+ border-radius: 20px;
117
+ font-size: 0.875rem;
118
+ font-weight: 500;
119
+ }
120
+
121
+ .property-content {
122
+ padding: 1.5rem;
123
+ }
124
+
125
+ .property-title {
126
+ font-size: 1.25rem;
127
+ font-weight: 600;
128
+ margin-bottom: 0.5rem;
129
+ color: var(--dark-color);
130
+ }
131
+
132
+ .property-price {
133
+ font-size: 1.5rem;
134
+ font-weight: 700;
135
+ color: var(--primary-color);
136
+ margin-bottom: 1rem;
137
+ }
138
+
139
+ .property-location {
140
+ color: var(--secondary-color);
141
+ margin-bottom: 1rem;
142
+ font-size: 0.875rem;
143
+ }
144
+
145
+ .property-features {
146
+ display: flex;
147
+ flex-wrap: wrap;
148
+ gap: 0.5rem;
149
+ margin-bottom: 1rem;
150
+ }
151
+
152
+ .feature-tag {
153
+ background: var(--light-color);
154
+ color: var(--secondary-color);
155
+ padding: 0.25rem 0.75rem;
156
+ border-radius: 15px;
157
+ font-size: 0.75rem;
158
+ font-weight: 500;
159
+ }
160
+
161
+ .feature-tag.available {
162
+ background: #dcfce7;
163
+ color: #166534;
164
+ }
165
+
166
+ .property-actions {
167
+ display: flex;
168
+ gap: 0.5rem;
169
+ margin-top: 1rem;
170
+ }
171
+
172
+ .btn-action {
173
+ flex: 1;
174
+ padding: 0.75rem;
175
+ border: none;
176
+ border-radius: 10px;
177
+ font-weight: 500;
178
+ transition: all 0.3s ease;
179
+ text-decoration: none;
180
+ text-align: center;
181
+ }
182
+
183
+ .btn-primary-custom {
184
+ background: var(--primary-color);
185
+ color: white;
186
+ }
187
+
188
+ .btn-primary-custom:hover {
189
+ background: #1d4ed8;
190
+ color: white;
191
+ }
192
+
193
+ .btn-outline-custom {
194
+ background: transparent;
195
+ color: var(--primary-color);
196
+ border: 2px solid var(--primary-color);
197
+ }
198
+
199
+ .btn-outline-custom:hover {
200
+ background: var(--primary-color);
201
+ color: white;
202
+ }
203
+
204
+ .floating-email {
205
+ position: fixed;
206
+ bottom: 30px;
207
+ right: 30px;
208
+ background: white;
209
+ border-radius: 15px;
210
+ padding: 1.5rem;
211
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
212
+ z-index: 1000;
213
+ max-width: 350px;
214
+ display: none;
215
+ border: 2px solid var(--primary-color);
216
+ }
217
+
218
+ .floating-email.show {
219
+ display: block;
220
+ animation: slideIn 0.3s ease, pulse 2s infinite;
221
+ }
222
+
223
+ .floating-email.hidden {
224
+ display: none !important;
225
+ }
226
+
227
+ @keyframes pulse {
228
+ 0% {
229
+ box-shadow: 0 10px 30px rgba(37, 99, 235, 0.2);
230
+ }
231
+ 50% {
232
+ box-shadow: 0 10px 30px rgba(37, 99, 235, 0.4);
233
+ }
234
+ 100% {
235
+ box-shadow: 0 10px 30px rgba(37, 99, 235, 0.2);
236
+ }
237
+ }
238
+
239
+ @keyframes slideIn {
240
+ from {
241
+ transform: translateX(100%);
242
+ opacity: 0;
243
+ }
244
+ to {
245
+ transform: translateX(0);
246
+ opacity: 1;
247
+ }
248
+ }
249
+
250
+ .email-header {
251
+ display: flex;
252
+ align-items: center;
253
+ gap: 0.5rem;
254
+ margin-bottom: 1rem;
255
+ }
256
+
257
+ .email-icon {
258
+ width: 40px;
259
+ height: 40px;
260
+ background: var(--primary-color);
261
+ border-radius: 50%;
262
+ display: flex;
263
+ align-items: center;
264
+ justify-content: center;
265
+ color: white;
266
+ }
267
+
268
+ .loading-spinner {
269
+ display: none;
270
+ text-align: center;
271
+ padding: 2rem;
272
+ }
273
+
274
+ .spinner {
275
+ width: 40px;
276
+ height: 40px;
277
+ border: 4px solid var(--border-color);
278
+ border-top: 4px solid var(--primary-color);
279
+ border-radius: 50%;
280
+ animation: spin 1s linear infinite;
281
+ }
282
+
283
+ @keyframes spin {
284
+ 0% { transform: rotate(0deg); }
285
+ 100% { transform: rotate(360deg); }
286
+ }
287
+
288
+ .recommendations-section {
289
+ background: white;
290
+ border-radius: 20px;
291
+ padding: 2rem;
292
+ margin-top: 3rem;
293
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
294
+ }
295
+
296
+ .section-title {
297
+ font-size: 1.5rem;
298
+ font-weight: 600;
299
+ margin-bottom: 1rem;
300
+ color: var(--dark-color);
301
+ }
302
+
303
+ .stats-grid {
304
+ display: grid;
305
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
306
+ gap: 1rem;
307
+ margin-bottom: 2rem;
308
+ }
309
+
310
+ .stat-card {
311
+ background: var(--light-color);
312
+ padding: 1.5rem;
313
+ border-radius: 15px;
314
+ text-align: center;
315
+ }
316
+
317
+ .stat-number {
318
+ font-size: 2rem;
319
+ font-weight: 700;
320
+ color: var(--primary-color);
321
+ }
322
+
323
+ .stat-label {
324
+ color: var(--secondary-color);
325
+ font-size: 0.875rem;
326
+ margin-top: 0.5rem;
327
+ }
328
+
329
+ .whatsapp-float {
330
+ position: fixed;
331
+ bottom: 30px;
332
+ left: 30px;
333
+ background: #25d366;
334
+ color: white;
335
+ width: 60px;
336
+ height: 60px;
337
+ border-radius: 50%;
338
+ display: flex;
339
+ align-items: center;
340
+ justify-content: center;
341
+ font-size: 1.5rem;
342
+ box-shadow: 0 5px 15px rgba(37, 214, 102, 0.3);
343
+ transition: all 0.3s ease;
344
+ z-index: 1000;
345
+ }
346
+
347
+ .whatsapp-float:hover {
348
+ transform: scale(1.1);
349
+ color: white;
350
+ }
351
+
352
+ .visit-history-float {
353
+ position: fixed;
354
+ bottom: 100px;
355
+ left: 30px;
356
+ background: #6c757d;
357
+ color: white;
358
+ width: 60px;
359
+ height: 60px;
360
+ border-radius: 50%;
361
+ display: flex;
362
+ align-items: center;
363
+ justify-content: center;
364
+ font-size: 1.5rem;
365
+ box-shadow: 0 5px 15px rgba(108, 117, 125, 0.3);
366
+ transition: all 0.3s ease;
367
+ z-index: 1000;
368
+ cursor: pointer;
369
+ }
370
+
371
+ .visit-history-float:hover {
372
+ transform: scale(1.1);
373
+ background: #495057;
374
+ }
375
+
376
+ .dark-mode-toggle {
377
+ position: fixed;
378
+ top: 100px;
379
+ right: 30px;
380
+ background: white;
381
+ border: none;
382
+ border-radius: 50%;
383
+ width: 50px;
384
+ height: 50px;
385
+ display: flex;
386
+ align-items: center;
387
+ justify-content: center;
388
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
389
+ z-index: 1000;
390
+ transition: all 0.3s ease;
391
+ }
392
+
393
+ .dark-mode-toggle:hover {
394
+ transform: scale(1.1);
395
+ }
396
+
397
+ /* Dark mode styles */
398
+ body.dark-mode {
399
+ background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
400
+ color: #f1f5f9;
401
+ }
402
+
403
+ body.dark-mode .navbar {
404
+ background: rgba(30, 41, 59, 0.95) !important;
405
+ }
406
+
407
+ body.dark-mode .property-card,
408
+ body.dark-mode .search-container,
409
+ body.dark-mode .recommendations-section,
410
+ body.dark-mode .floating-email {
411
+ background: #334155;
412
+ color: #f1f5f9;
413
+ }
414
+
415
+ body.dark-mode .property-title {
416
+ color: #f1f5f9;
417
+ }
418
+
419
+ body.dark-mode .stat-card {
420
+ background: #475569;
421
+ }
422
+
423
+ /* Responsive design */
424
+ @media (max-width: 768px) {
425
+ .hero-title {
426
+ font-size: 2.5rem;
427
+ }
428
+
429
+ .property-grid {
430
+ grid-template-columns: 1fr;
431
+ }
432
+
433
+ .floating-email {
434
+ bottom: 20px;
435
+ right: 20px;
436
+ left: 20px;
437
+ max-width: none;
438
+ }
439
+
440
+ .whatsapp-float {
441
+ bottom: 20px;
442
+ left: 20px;
443
+ }
444
+ }
445
+ </style>
446
+ </head>
447
+ <body>
448
+ <!-- Navigation -->
449
+ <nav class="navbar navbar-expand-lg navbar-light">
450
+ <div class="container">
451
+ <a class="navbar-brand fw-bold" href="#">
452
+ <i class="fas fa-home me-2"></i>
453
+ Real Estate Lead Engine
454
+ </a>
455
+
456
+ <div class="navbar-nav ms-auto">
457
+ <a class="nav-link" href="#properties">Properties</a>
458
+ <a class="nav-link" href="#recommendations">Recommendations</a>
459
+ <a class="nav-link" href="#" onclick="showVisitHistory()">Visit History</a>
460
+ <a class="nav-link" href="/analytics">Analytics</a>
461
+ </div>
462
+ </div>
463
+ </nav>
464
+
465
+ <!-- Hero Section -->
466
+ <section class="hero-section">
467
+ <div class="container">
468
+ <h1 class="hero-title">Discover Your Perfect Property</h1>
469
+ <p class="hero-subtitle">Explore thousands of properties with smart recommendations based on your preferences</p>
470
+
471
+ <div class="search-container">
472
+ <div class="row">
473
+ <div class="col-md-3">
474
+ <select class="form-select" id="propertyType">
475
+ <option value="">All Property Types</option>
476
+ <option value="Flat">Flat</option>
477
+ <option value="Villa">Villa</option>
478
+ <option value="House">House</option>
479
+ <option value="Apartment">Apartment</option>
480
+ </select>
481
+ </div>
482
+ <div class="col-md-3">
483
+ <input type="number" class="form-control" id="minPrice" placeholder="Min Price">
484
+ </div>
485
+ <div class="col-md-3">
486
+ <input type="number" class="form-control" id="maxPrice" placeholder="Max Price">
487
+ </div>
488
+ <div class="col-md-3">
489
+ <button class="btn btn-primary w-100" onclick="searchProperties()">
490
+ <i class="fas fa-search me-2"></i>Search
491
+ </button>
492
+ </div>
493
+ </div>
494
+ </div>
495
+ </div>
496
+ </section>
497
+
498
+ <!-- Testing Controls -->
499
+ <div class="container mb-4">
500
+ <div class="row">
501
+ <div class="col-12">
502
+ <div class="alert alert-info">
503
+ <h6>πŸ§ͺ Testing Controls:</h6>
504
+ <button class="btn btn-sm btn-danger me-2" onclick="clearTrackingData()">
505
+ <i class="fas fa-trash"></i> Clear Data
506
+ </button>
507
+ <button class="btn btn-sm btn-warning me-2" onclick="testModal()">
508
+ <i class="fas fa-eye"></i> Test Modal
509
+ </button>
510
+ <button class="btn btn-sm btn-secondary me-2" onclick="testPropertyClick()">
511
+ <i class="fas fa-mouse-pointer"></i> Test Click
512
+ </button>
513
+ <button class="btn btn-sm btn-info me-2" onclick="showTrackingData()">
514
+ <i class="fas fa-chart-bar"></i> Show Data
515
+ </button>
516
+ <small class="d-block mt-2">
517
+ <strong>Expected Behavior:</strong> Card clicks = tracked + opens modal, "View Details" = same behavior, NO DUPLICATES
518
+ </small>
519
+ </div>
520
+ </div>
521
+ </div>
522
+ </div>
523
+
524
+ <!-- Properties Section -->
525
+ <section class="container" id="properties">
526
+ <div class="loading-spinner" id="loadingSpinner">
527
+ <div class="spinner"></div>
528
+ <p class="mt-3">Loading properties...</p>
529
+ </div>
530
+
531
+ <div class="property-grid" id="propertyGrid"></div>
532
+
533
+ <div class="text-center mt-4">
534
+ <button class="btn btn-outline-primary" id="loadMoreBtn" onclick="loadMoreProperties()" style="display: none;">
535
+ Load More Properties
536
+ </button>
537
+ </div>
538
+ </section>
539
+
540
+ <!-- Recommendations Section -->
541
+ <section class="container" id="recommendations">
542
+ <div class="recommendations-section">
543
+ <h2 class="section-title">
544
+ <i class="fas fa-star me-2"></i>
545
+ Personalized Recommendations
546
+ </h2>
547
+
548
+ <div class="stats-grid">
549
+ <div class="stat-card">
550
+ <div class="stat-number" id="propertiesViewed">0</div>
551
+ <div class="stat-label">Properties Viewed</div>
552
+ </div>
553
+ <div class="stat-card">
554
+ <div class="stat-number" id="timeSpent">0</div>
555
+ <div class="stat-label">Minutes Spent</div>
556
+ </div>
557
+ <div class="stat-card">
558
+ <div class="stat-number" id="leadScore">0</div>
559
+ <div class="stat-label">Lead Score</div>
560
+ </div>
561
+ <div class="stat-card">
562
+ <div class="stat-number" id="visitRequests">0</div>
563
+ <div class="stat-label">Visit Requests</div>
564
+ </div>
565
+ </div>
566
+
567
+ <div class="property-grid" id="recommendationsGrid"></div>
568
+ </div>
569
+ </section>
570
+
571
+ <!-- Floating Email Capture -->
572
+ <div class="floating-email" id="floatingEmail">
573
+ <div class="email-header">
574
+ <div class="email-icon">
575
+ <i class="fas fa-envelope"></i>
576
+ </div>
577
+ <div>
578
+ <h5 class="mb-0">Get Your Perfect Matches!</h5>
579
+ <small class="text-muted">Receive personalized property recommendations</small>
580
+ </div>
581
+ <button type="button" class="btn-close" onclick="hideFloatingEmail()" style="position: absolute; top: 10px; right: 10px;"></button>
582
+ </div>
583
+
584
+ <form id="emailForm">
585
+ <div class="mb-3">
586
+ <input type="email" class="form-control" id="userEmail" placeholder="Enter your email" required>
587
+ </div>
588
+ <button type="submit" class="btn btn-primary w-100">
589
+ <i class="fas fa-paper-plane me-2"></i>
590
+ Send Recommendations
591
+ </button>
592
+ </form>
593
+ </div>
594
+
595
+ <!-- WhatsApp Float -->
596
+ <a href="https://wa.me/919876543210" class="whatsapp-float" target="_blank">
597
+ <i class="fab fa-whatsapp"></i>
598
+ </a>
599
+
600
+ <!-- Visit History Float -->
601
+ <div class="visit-history-float" onclick="showVisitHistory()">
602
+ <i class="fas fa-history"></i>
603
+ </div>
604
+
605
+ <!-- Dark Mode Toggle -->
606
+ <button class="dark-mode-toggle" onclick="toggleDarkMode()">
607
+ <i class="fas fa-moon" id="darkModeIcon"></i>
608
+ </button>
609
+
610
+ <!-- Property Details Modal -->
611
+ <div class="modal fade" id="propertyDetailsModal" tabindex="-1" aria-labelledby="propertyDetailsModalLabel" aria-hidden="true">
612
+ <div class="modal-dialog modal-xl">
613
+ <div class="modal-content">
614
+ <div class="modal-header">
615
+ <h5 class="modal-title" id="propertyDetailsModalLabel">
616
+ <i class="fas fa-home me-2"></i>Property Details
617
+ </h5>
618
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
619
+ </div>
620
+ <div class="modal-body">
621
+ <div class="row">
622
+ <div class="col-md-8">
623
+ <div id="propertyImages" class="mb-4">
624
+ <!-- Property images will be loaded here -->
625
+ </div>
626
+ <div id="propertyDescription" class="mb-4">
627
+ <!-- Property description will be loaded here -->
628
+ </div>
629
+ </div>
630
+ <div class="col-md-4">
631
+ <div class="property-details-sidebar">
632
+ <div class="card">
633
+ <div class="card-body">
634
+ <h5 id="detailPropertyName" class="card-title"></h5>
635
+ <h4 id="detailPropertyPrice" class="text-primary mb-3"></h4>
636
+ <div class="property-meta mb-3">
637
+ <div class="row">
638
+ <div class="col-6">
639
+ <small class="text-muted">Type</small>
640
+ <p id="detailPropertyType" class="mb-0"></p>
641
+ </div>
642
+ <div class="col-6">
643
+ <small class="text-muted">Location</small>
644
+ <p id="detailPropertyAddress" class="mb-0"></p>
645
+ </div>
646
+ </div>
647
+ <div class="row mt-2">
648
+ <div class="col-4">
649
+ <small class="text-muted">Beds</small>
650
+ <p id="detailPropertyBeds" class="mb-0"></p>
651
+ </div>
652
+ <div class="col-4">
653
+ <small class="text-muted">Baths</small>
654
+ <p id="detailPropertyBaths" class="mb-0"></p>
655
+ </div>
656
+ <div class="col-4">
657
+ <small class="text-muted">Size</small>
658
+ <p id="detailPropertySize" class="mb-0"></p>
659
+ </div>
660
+ </div>
661
+ </div>
662
+ <div class="property-features mb-3">
663
+ <h6>Features</h6>
664
+ <div id="detailPropertyFeatures"></div>
665
+ </div>
666
+ <div class="d-grid gap-2">
667
+ <button type="button" class="btn btn-primary" onclick="openVisitModalFromDetails()">
668
+ <i class="fas fa-calendar-check me-2"></i>Schedule Visit
669
+ </button>
670
+ <button type="button" class="btn btn-outline-primary" onclick="addToFavorites()">
671
+ <i class="fas fa-heart me-2"></i>Add to Favorites
672
+ </button>
673
+ </div>
674
+ </div>
675
+ </div>
676
+ </div>
677
+ </div>
678
+ </div>
679
+ </div>
680
+ <div class="modal-footer">
681
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
682
+ <i class="fas fa-times me-2"></i>Close
683
+ </button>
684
+ <button type="button" class="btn btn-success" onclick="openVisitModalFromDetails()">
685
+ <i class="fas fa-calendar-check me-2"></i>Schedule Visit
686
+ </button>
687
+ <button type="button" class="btn btn-primary" onclick="shareProperty()">
688
+ <i class="fas fa-share me-2"></i>Share
689
+ </button>
690
+ </div>
691
+ </div>
692
+ </div>
693
+ </div>
694
+
695
+ <!-- Visit Modal -->
696
+ <div class="modal fade" id="visitModal" tabindex="-1" aria-labelledby="visitModalLabel" aria-hidden="true">
697
+ <div class="modal-dialog modal-lg">
698
+ <div class="modal-content">
699
+ <div class="modal-header">
700
+ <h5 class="modal-title" id="visitModalLabel">
701
+ <i class="fas fa-calendar-check me-2"></i>Schedule Property Visit
702
+ </h5>
703
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
704
+ </div>
705
+ <div class="modal-body">
706
+ <div class="row">
707
+ <div class="col-md-6">
708
+ <div class="property-summary mb-4">
709
+ <h6 class="text-muted mb-2">Property Details</h6>
710
+ <div class="property-info">
711
+ <h5 id="modalPropertyName" class="mb-2"></h5>
712
+ <p class="text-primary mb-1" id="modalPropertyPrice"></p>
713
+ <p class="text-muted mb-1" id="modalPropertyType"></p>
714
+ <p class="text-muted mb-3" id="modalPropertyAddress"></p>
715
+ </div>
716
+ </div>
717
+ </div>
718
+ <div class="col-md-6">
719
+ <form id="visitForm">
720
+ <div class="mb-3">
721
+ <label for="visitorName" class="form-label">Your Name *</label>
722
+ <input type="text" class="form-control" id="visitorName" required>
723
+ </div>
724
+ <div class="mb-3">
725
+ <label for="visitorEmail" class="form-label">Email Address *</label>
726
+ <input type="email" class="form-control" id="visitorEmail" required>
727
+ </div>
728
+ <div class="mb-3">
729
+ <label for="visitorPhone" class="form-label">Phone Number *</label>
730
+ <input type="tel" class="form-control" id="visitorPhone" required>
731
+ </div>
732
+ <div class="row">
733
+ <div class="col-md-6">
734
+ <div class="mb-3">
735
+ <label for="visitDate" class="form-label">Preferred Date *</label>
736
+ <input type="date" class="form-control" id="visitDate" required>
737
+ </div>
738
+ </div>
739
+ <div class="col-md-6">
740
+ <div class="mb-3">
741
+ <label for="visitTime" class="form-label">Preferred Time *</label>
742
+ <select class="form-select" id="visitTime" required>
743
+ <option value="">Select Time</option>
744
+ <option value="09:00">09:00 AM</option>
745
+ <option value="10:00">10:00 AM</option>
746
+ <option value="11:00">11:00 AM</option>
747
+ <option value="12:00">12:00 PM</option>
748
+ <option value="14:00">02:00 PM</option>
749
+ <option value="15:00">03:00 PM</option>
750
+ <option value="16:00">04:00 PM</option>
751
+ <option value="17:00">05:00 PM</option>
752
+ <option value="18:00">06:00 PM</option>
753
+ </select>
754
+ </div>
755
+ </div>
756
+ </div>
757
+ <div class="mb-3">
758
+ <label for="visitNotes" class="form-label">Additional Notes</label>
759
+ <textarea class="form-control" id="visitNotes" rows="3" placeholder="Any specific requirements or questions..."></textarea>
760
+ </div>
761
+ <div class="mb-3">
762
+ <div class="form-check">
763
+ <input class="form-check-input" type="checkbox" id="agreeTerms" required>
764
+ <label class="form-check-label" for="agreeTerms">
765
+ I agree to receive communications about this property visit
766
+ </label>
767
+ </div>
768
+ </div>
769
+ </form>
770
+ </div>
771
+ </div>
772
+ </div>
773
+ <div class="modal-footer">
774
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
775
+ <i class="fas fa-times me-2"></i>Cancel
776
+ </button>
777
+ <button type="button" class="btn btn-primary" onclick="submitVisitRequest()">
778
+ <i class="fas fa-calendar-plus me-2"></i>Schedule Visit
779
+ </button>
780
+ </div>
781
+ </div>
782
+ </div>
783
+ </div>
784
+
785
+ <!-- Bootstrap JS -->
786
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
787
+
788
+ <!-- Custom JS -->
789
+ <script>
790
+ // Global variables
791
+ let currentPage = 1;
792
+ let allProperties = [];
793
+ let userSessionId = null;
794
+ let userEmail = null;
795
+ let isDarkMode = false;
796
+
797
+ // Initialize the application
798
+ document.addEventListener('DOMContentLoaded', function() {
799
+ loadProperties();
800
+ initializeDetailedViewTracking();
801
+
802
+ // Show email form immediately
803
+ showEmailFormImmediately();
804
+
805
+ // Track page load
806
+ trackActivity('page_load', {
807
+ page: 'home',
808
+ timestamp: new Date().toISOString()
809
+ });
810
+ });
811
+
812
+ function initializeApp() {
813
+ // Generate session ID if not exists
814
+ userSessionId = localStorage.getItem('userSessionId') || generateSessionId();
815
+ localStorage.setItem('userSessionId', userSessionId);
816
+
817
+ // Load dark mode preference
818
+ isDarkMode = localStorage.getItem('darkMode') === 'true';
819
+ if (isDarkMode) {
820
+ document.body.classList.add('dark-mode');
821
+ document.getElementById('darkModeIcon').className = 'fas fa-sun';
822
+ }
823
+
824
+ // Load properties
825
+ loadProperties();
826
+
827
+ // Show floating email after 30 seconds
828
+ setTimeout(() => {
829
+ showFloatingEmail();
830
+ }, 30000);
831
+
832
+ // Track page view
833
+ trackActivity('page_view', { page: 'home' });
834
+ }
835
+
836
+ function generateSessionId() {
837
+ return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
838
+ }
839
+
840
+ async function loadProperties(page = 1, append = false) {
841
+ try {
842
+ showLoading(true);
843
+
844
+ const params = new URLSearchParams({
845
+ page: page,
846
+ page_size: 20
847
+ });
848
+
849
+ const response = await fetch(`/api/properties?${params}`);
850
+ const data = await response.json();
851
+
852
+ if (data.properties) {
853
+ if (append) {
854
+ allProperties = allProperties.concat(data.properties);
855
+ } else {
856
+ allProperties = data.properties;
857
+ }
858
+
859
+ renderProperties(allProperties);
860
+
861
+ // Show/hide load more button
862
+ const loadMoreBtn = document.getElementById('loadMoreBtn');
863
+ if (data.page < data.total_pages) {
864
+ loadMoreBtn.style.display = 'block';
865
+ } else {
866
+ loadMoreBtn.style.display = 'none';
867
+ }
868
+ }
869
+
870
+ } catch (error) {
871
+ console.error('Error loading properties:', error);
872
+ showError('Failed to load properties');
873
+ } finally {
874
+ showLoading(false);
875
+ }
876
+ }
877
+
878
+ function renderProperties(properties) {
879
+ const grid = document.getElementById('propertyGrid');
880
+ grid.innerHTML = '';
881
+
882
+ properties.forEach(property => {
883
+ const card = createPropertyCard(property);
884
+ grid.appendChild(card);
885
+ });
886
+
887
+ // Initialize detailed view tracking after properties are rendered
888
+ setTimeout(() => {
889
+ addDetailedViewTracking();
890
+ }, 500);
891
+ }
892
+
893
+ function createPropertyCard(property) {
894
+ const card = document.createElement('div');
895
+ card.className = 'property-card';
896
+ card.onclick = () => viewProperty(property);
897
+
898
+ const features = property.features || [];
899
+ // Use property images if available, otherwise show no image
900
+ const images = property.propertyImages || [];
901
+
902
+ card.innerHTML = `
903
+ <div class="position-relative">
904
+ ${images.length > 0 ? `
905
+ <img src="${images[0]}"
906
+ alt="${property.propertyName}"
907
+ class="property-image"
908
+ onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
909
+ ` : ''}
910
+ <div class="property-badge">${property.typeName || 'Property'}</div>
911
+ ${images.length === 0 ? `
912
+ <div class="no-image-placeholder" style="width: 100%; height: 250px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 18px;">
913
+ <i class="fas fa-home me-2"></i>${property.propertyName || 'Property'}
914
+ </div>
915
+ ` : ''}
916
+ </div>
917
+ <div class="property-content">
918
+ <h5 class="property-title">${property.propertyName || 'Property'}</h5>
919
+ <div class="property-price">β‚Ή${(property.marketValue || 0).toLocaleString()}</div>
920
+ <div class="property-location">
921
+ <i class="fas fa-map-marker-alt me-2"></i>
922
+ ${property.address || 'Location not specified'}
923
+ </div>
924
+ <div class="property-features">
925
+ ${features.slice(0, 3).map(feature =>
926
+ `<span class="feature-tag available">${feature}</span>`
927
+ ).join('')}
928
+ ${features.length > 3 ? `<span class="feature-tag">+${features.length - 3} more</span>` : ''}
929
+ </div>
930
+ <div class="property-actions">
931
+ <button class="btn-action btn-primary-custom" onclick="event.stopPropagation(); scheduleVisit('${property.id}', '${property.propertyName}', '${property.marketValue}', '${property.typeName}', '${property.address}')">
932
+ <i class="fas fa-calendar me-2"></i>Schedule Visit
933
+ </button>
934
+ <button class="btn-action btn-outline-custom" onclick="event.stopPropagation(); contactAgent('${property.id}')">
935
+ <i class="fas fa-phone me-2"></i>Contact
936
+ </button>
937
+ </div>
938
+ </div>
939
+ `;
940
+
941
+ return card;
942
+ }
943
+
944
+ function viewProperty(property) {
945
+ console.log('πŸ” Property clicked:', property.propertyName, 'ID:', property.id);
946
+
947
+ // Track property click (not just view)
948
+ trackActivity('property_click', {
949
+ property: property
950
+ });
951
+
952
+ // Track property type preference
953
+ if (property.typeName) {
954
+ trackActivity('property_type_select', { propertyType: property.typeName });
955
+ }
956
+
957
+ // Track price range preference
958
+ if (property.marketValue) {
959
+ const priceRange = getPriceRange(property.marketValue);
960
+ trackActivity('price_range_select', { priceRange: priceRange });
961
+ }
962
+
963
+ // Show property details modal
964
+ showPropertyDetailsModal(property);
965
+
966
+ console.log('βœ… Property click tracked and modal opened for:', property.propertyName);
967
+ }
968
+
969
+ function scheduleVisit(propertyId, propertyName, propertyPrice, propertyType, propertyAddress) {
970
+ // Open visit modal directly
971
+ openVisitModal(propertyId, propertyName, propertyPrice, propertyType, propertyAddress);
972
+ }
973
+
974
+ function contactAgent(propertyId) {
975
+ trackActivity('contact_agent', { property_id: propertyId });
976
+ // Implement contact agent functionality
977
+ alert('Agent contact feature coming soon!');
978
+ }
979
+
980
+ // Global variables for visit modal
981
+ let currentVisitProperty = null;
982
+ let modalOpenTime = null;
983
+ let visitModal = null;
984
+ let visitHistory = [];
985
+ let userPreferences = {};
986
+
987
+ function openVisitModal(propertyId, propertyName, propertyPrice, propertyType, propertyAddress) {
988
+ // Store current property info
989
+ currentVisitProperty = {
990
+ id: propertyId,
991
+ name: propertyName,
992
+ price: propertyPrice,
993
+ type: propertyType,
994
+ address: propertyAddress,
995
+ timestamp: new Date().toISOString()
996
+ };
997
+
998
+ // Set modal content
999
+ document.getElementById('modalPropertyName').textContent = propertyName || 'Property';
1000
+ document.getElementById('modalPropertyPrice').textContent = `β‚Ή${parseInt(propertyPrice || 0).toLocaleString()}`;
1001
+ document.getElementById('modalPropertyType').textContent = propertyType || 'Property';
1002
+ document.getElementById('modalPropertyAddress').textContent = propertyAddress || 'Location not specified';
1003
+
1004
+ // Set minimum date to today
1005
+ const today = new Date().toISOString().split('T')[0];
1006
+ document.getElementById('visitDate').min = today;
1007
+ document.getElementById('visitDate').value = today;
1008
+
1009
+ // Load user preferences from localStorage
1010
+ loadUserPreferences();
1011
+
1012
+ // Pre-fill form with saved data if available
1013
+ prefillVisitForm();
1014
+
1015
+ // Track modal open
1016
+ trackActivity('visit_modal_open', {
1017
+ property_id: propertyId,
1018
+ property_name: propertyName,
1019
+ property_price: propertyPrice,
1020
+ property_type: propertyType,
1021
+ property_address: propertyAddress
1022
+ });
1023
+
1024
+ // Store visit attempt in localStorage
1025
+ storeVisitAttempt(currentVisitProperty);
1026
+
1027
+ // Record modal open time
1028
+ modalOpenTime = Date.now();
1029
+
1030
+ // Show modal
1031
+ visitModal = new bootstrap.Modal(document.getElementById('visitModal'));
1032
+ visitModal.show();
1033
+
1034
+ // Track modal duration when closed
1035
+ document.getElementById('visitModal').addEventListener('hidden.bs.modal', function() {
1036
+ if (modalOpenTime) {
1037
+ const duration = Date.now() - modalOpenTime;
1038
+ trackActivity('visit_modal_duration', {
1039
+ property_id: propertyId,
1040
+ duration: duration,
1041
+ modal_action: 'closed'
1042
+ });
1043
+
1044
+ // Store modal interaction data
1045
+ storeModalInteraction({
1046
+ property_id: propertyId,
1047
+ duration: duration,
1048
+ action: 'closed',
1049
+ timestamp: new Date().toISOString()
1050
+ });
1051
+
1052
+ modalOpenTime = null;
1053
+ }
1054
+ });
1055
+ }
1056
+
1057
+ function submitVisitRequest() {
1058
+ // Get form data
1059
+ const formData = {
1060
+ visitorName: document.getElementById('visitorName').value,
1061
+ visitorEmail: document.getElementById('visitorEmail').value,
1062
+ visitorPhone: document.getElementById('visitorPhone').value,
1063
+ visitDate: document.getElementById('visitDate').value,
1064
+ visitTime: document.getElementById('visitTime').value,
1065
+ visitNotes: document.getElementById('visitNotes').value,
1066
+ agreeTerms: document.getElementById('agreeTerms').checked
1067
+ };
1068
+
1069
+ // Validate form
1070
+ if (!formData.visitorName || !formData.visitorEmail || !formData.visitorPhone ||
1071
+ !formData.visitDate || !formData.visitTime || !formData.agreeTerms) {
1072
+ showNotification('Please fill in all required fields', 'warning');
1073
+ return;
1074
+ }
1075
+
1076
+ // Calculate modal duration
1077
+ const duration = modalOpenTime ? Date.now() - modalOpenTime : 0;
1078
+
1079
+ // Store user preferences in localStorage
1080
+ storeUserPreferences(formData);
1081
+
1082
+ // Store visit request in localStorage
1083
+ const visitRequest = {
1084
+ ...currentVisitProperty,
1085
+ visitor_data: formData,
1086
+ modal_duration: duration,
1087
+ submitted_at: new Date().toISOString(),
1088
+ status: 'submitted'
1089
+ };
1090
+ storeVisitRequest(visitRequest);
1091
+
1092
+ // Track visit request submission
1093
+ trackActivity('visit_request_submitted', {
1094
+ property_id: currentVisitProperty.id,
1095
+ property_name: currentVisitProperty.name,
1096
+ property_price: currentVisitProperty.price,
1097
+ property_type: currentVisitProperty.type,
1098
+ property_address: currentVisitProperty.address,
1099
+ visitor_name: formData.visitorName,
1100
+ visitor_email: formData.visitorEmail,
1101
+ visitor_phone: formData.visitorPhone,
1102
+ visit_date: formData.visitDate,
1103
+ visit_time: formData.visitTime,
1104
+ visit_notes: formData.visitNotes,
1105
+ modal_duration: duration,
1106
+ form_data: formData
1107
+ });
1108
+
1109
+ // Update user email for future tracking
1110
+ userEmail = formData.visitorEmail;
1111
+
1112
+ // Show success message
1113
+ showNotification('Visit request submitted successfully! We will contact you soon.', 'success');
1114
+
1115
+ // Close modal
1116
+ visitModal.hide();
1117
+
1118
+ // Reset form
1119
+ document.getElementById('visitForm').reset();
1120
+
1121
+ // Send confirmation email (if email service is configured)
1122
+ sendVisitConfirmationEmail(formData);
1123
+
1124
+ // Update visit history display
1125
+ updateVisitHistoryDisplay();
1126
+ }
1127
+
1128
+ function sendVisitConfirmationEmail(formData) {
1129
+ // This would integrate with your email service
1130
+ console.log('Sending visit confirmation email to:', formData.visitorEmail);
1131
+
1132
+ // You can implement email sending here or call your backend API
1133
+ fetch('/api/send-visit-confirmation', {
1134
+ method: 'POST',
1135
+ headers: {
1136
+ 'Content-Type': 'application/json',
1137
+ },
1138
+ body: JSON.stringify({
1139
+ visitor_data: formData,
1140
+ property_data: currentVisitProperty
1141
+ })
1142
+ }).catch(error => {
1143
+ console.error('Error sending visit confirmation:', error);
1144
+ });
1145
+ }
1146
+
1147
+ function bookVisit(propertyId) {
1148
+ // Fallback function for backward compatibility
1149
+ trackActivity('book_visit', { property_id: propertyId });
1150
+ showNotification('Please use the Visit button to schedule a property visit', 'info');
1151
+ }
1152
+
1153
+ // ===== LOCALSTORAGE FUNCTIONS =====
1154
+
1155
+ function storeUserPreferences(formData) {
1156
+ const preferences = {
1157
+ visitorName: formData.visitorName,
1158
+ visitorEmail: formData.visitorEmail,
1159
+ visitorPhone: formData.visitorPhone,
1160
+ lastUpdated: new Date().toISOString()
1161
+ };
1162
+ localStorage.setItem('userPreferences', JSON.stringify(preferences));
1163
+ }
1164
+
1165
+ function loadUserPreferences() {
1166
+ const saved = localStorage.getItem('userPreferences');
1167
+ if (saved) {
1168
+ userPreferences = JSON.parse(saved);
1169
+ }
1170
+ return userPreferences;
1171
+ }
1172
+
1173
+ function prefillVisitForm() {
1174
+ if (userPreferences.visitorName) {
1175
+ document.getElementById('visitorName').value = userPreferences.visitorName;
1176
+ }
1177
+ if (userPreferences.visitorEmail) {
1178
+ document.getElementById('visitorEmail').value = userPreferences.visitorEmail;
1179
+ }
1180
+ if (userPreferences.visitorPhone) {
1181
+ document.getElementById('visitorPhone').value = userPreferences.visitorPhone;
1182
+ }
1183
+ }
1184
+
1185
+ function storeVisitAttempt(propertyData) {
1186
+ const attempts = JSON.parse(localStorage.getItem('visitAttempts') || '[]');
1187
+ attempts.push({
1188
+ ...propertyData,
1189
+ attempt_time: new Date().toISOString()
1190
+ });
1191
+ localStorage.setItem('visitAttempts', JSON.stringify(attempts));
1192
+ }
1193
+
1194
+ function storeVisitRequest(visitRequest) {
1195
+ const requests = JSON.parse(localStorage.getItem('visitRequests') || '[]');
1196
+ requests.push(visitRequest);
1197
+ localStorage.setItem('visitRequests', JSON.stringify(requests));
1198
+ }
1199
+
1200
+ function storeModalInteraction(interactionData) {
1201
+ const interactions = JSON.parse(localStorage.getItem('modalInteractions') || '[]');
1202
+ interactions.push(interactionData);
1203
+ localStorage.setItem('modalInteractions', JSON.stringify(interactions));
1204
+ }
1205
+
1206
+ function getVisitHistory() {
1207
+ return JSON.parse(localStorage.getItem('visitRequests') || '[]');
1208
+ }
1209
+
1210
+ function getVisitAttempts() {
1211
+ return JSON.parse(localStorage.getItem('visitAttempts') || '[]');
1212
+ }
1213
+
1214
+ function getModalInteractions() {
1215
+ return JSON.parse(localStorage.getItem('modalInteractions') || '[]');
1216
+ }
1217
+
1218
+ function clearVisitHistory() {
1219
+ localStorage.removeItem('visitRequests');
1220
+ localStorage.removeItem('visitAttempts');
1221
+ localStorage.removeItem('modalInteractions');
1222
+ showNotification('Visit history cleared', 'info');
1223
+ }
1224
+
1225
+ function updateVisitHistoryDisplay() {
1226
+ const history = getVisitHistory();
1227
+ const attempts = getVisitAttempts();
1228
+
1229
+ // Update analytics if available
1230
+ if (typeof updateVisitStats === 'function') {
1231
+ updateVisitStats(history, attempts);
1232
+ }
1233
+ }
1234
+
1235
+ function exportVisitData() {
1236
+ const data = {
1237
+ visitRequests: getVisitHistory(),
1238
+ visitAttempts: getVisitAttempts(),
1239
+ modalInteractions: getModalInteractions(),
1240
+ userPreferences: loadUserPreferences(),
1241
+ exportDate: new Date().toISOString()
1242
+ };
1243
+
1244
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
1245
+ const url = URL.createObjectURL(blob);
1246
+ const a = document.createElement('a');
1247
+ a.href = url;
1248
+ a.download = `visit-data-${new Date().toISOString().split('T')[0]}.json`;
1249
+ document.body.appendChild(a);
1250
+ a.click();
1251
+ document.body.removeChild(a);
1252
+ URL.revokeObjectURL(url);
1253
+
1254
+ showNotification('Visit data exported successfully', 'success');
1255
+ }
1256
+
1257
+ function showVisitHistory() {
1258
+ const history = getVisitHistory();
1259
+ const attempts = getVisitAttempts();
1260
+
1261
+ let historyHtml = '<div class="visit-history-modal">';
1262
+ historyHtml += '<h4>Visit History</h4>';
1263
+
1264
+ if (history.length === 0 && attempts.length === 0) {
1265
+ historyHtml += '<p>No visit history found.</p>';
1266
+ } else {
1267
+ // Show submitted requests
1268
+ if (history.length > 0) {
1269
+ historyHtml += '<h5>Submitted Requests</h5>';
1270
+ history.forEach((request, index) => {
1271
+ historyHtml += `
1272
+ <div class="visit-item">
1273
+ <strong>${request.name}</strong><br>
1274
+ <small>Date: ${request.visitor_data.visit_date} at ${request.visitor_data.visit_time}</small><br>
1275
+ <small>Status: ${request.status}</small>
1276
+ </div>
1277
+ `;
1278
+ });
1279
+ }
1280
+
1281
+ // Show visit attempts
1282
+ if (attempts.length > 0) {
1283
+ historyHtml += '<h5>Visit Attempts</h5>';
1284
+ attempts.forEach((attempt, index) => {
1285
+ historyHtml += `
1286
+ <div class="visit-item">
1287
+ <strong>${attempt.name}</strong><br>
1288
+ <small>Attempted: ${new Date(attempt.attempt_time).toLocaleDateString()}</small>
1289
+ </div>
1290
+ `;
1291
+ });
1292
+ }
1293
+ }
1294
+
1295
+ historyHtml += '</div>';
1296
+
1297
+ // Show in modal or notification
1298
+ showNotification(historyHtml, 'info', 10000);
1299
+ }
1300
+
1301
+ // Initialize localStorage data on page load
1302
+ function initializeLocalStorage() {
1303
+ loadUserPreferences();
1304
+ updateVisitHistoryDisplay();
1305
+ }
1306
+
1307
+ // Call initialization
1308
+ initializeLocalStorage();
1309
+
1310
+ function searchProperties() {
1311
+ const propertyType = document.getElementById('propertyType').value;
1312
+ const minPrice = document.getElementById('minPrice').value;
1313
+ const maxPrice = document.getElementById('maxPrice').value;
1314
+
1315
+ // Track search
1316
+ trackActivity('property_search', {
1317
+ property_type: propertyType,
1318
+ min_price: minPrice,
1319
+ max_price: maxPrice
1320
+ });
1321
+
1322
+ // Filter properties
1323
+ let filtered = allProperties;
1324
+
1325
+ if (propertyType) {
1326
+ filtered = filtered.filter(p => p.typeName === propertyType);
1327
+ }
1328
+ if (minPrice) {
1329
+ filtered = filtered.filter(p => p.marketValue >= parseInt(minPrice));
1330
+ }
1331
+ if (maxPrice) {
1332
+ filtered = filtered.filter(p => p.marketValue <= parseInt(maxPrice));
1333
+ }
1334
+
1335
+ renderProperties(filtered);
1336
+ }
1337
+
1338
+ function loadMoreProperties() {
1339
+ currentPage++;
1340
+ loadProperties(currentPage, true);
1341
+ }
1342
+
1343
+ function showFloatingEmail() {
1344
+ const emailDiv = document.getElementById('floatingEmail');
1345
+ if (emailDiv) {
1346
+ emailDiv.classList.remove('hidden');
1347
+ emailDiv.classList.add('show');
1348
+ console.log('Email form shown');
1349
+ }
1350
+ }
1351
+
1352
+ function hideFloatingEmail() {
1353
+ const emailDiv = document.getElementById('floatingEmail');
1354
+ if (emailDiv) {
1355
+ emailDiv.classList.remove('show');
1356
+ emailDiv.classList.add('hidden');
1357
+ }
1358
+ }
1359
+
1360
+ // Show email form immediately on page load
1361
+ function showEmailFormImmediately() {
1362
+ setTimeout(() => {
1363
+ showFloatingEmail();
1364
+ }, 1000);
1365
+ }
1366
+
1367
+ function toggleDarkMode() {
1368
+ isDarkMode = !isDarkMode;
1369
+ document.body.classList.toggle('dark-mode', isDarkMode);
1370
+
1371
+ const icon = document.getElementById('darkModeIcon');
1372
+ icon.className = isDarkMode ? 'fas fa-sun' : 'fas fa-moon';
1373
+
1374
+ localStorage.setItem('darkMode', isDarkMode);
1375
+ }
1376
+
1377
+ function showLoading(show) {
1378
+ const spinner = document.getElementById('loadingSpinner');
1379
+ const grid = document.getElementById('propertyGrid');
1380
+
1381
+ if (show) {
1382
+ spinner.style.display = 'block';
1383
+ grid.style.display = 'none';
1384
+ } else {
1385
+ spinner.style.display = 'none';
1386
+ grid.style.display = 'grid';
1387
+ }
1388
+ }
1389
+
1390
+ function showError(message) {
1391
+ // Implement error notification
1392
+ console.error(message);
1393
+ }
1394
+
1395
+ // Show notification
1396
+ function showNotification(message, type = 'info') {
1397
+ const notification = document.createElement('div');
1398
+ notification.className = `alert alert-${type} alert-dismissible fade show`;
1399
+ notification.style.cssText = `
1400
+ position: fixed;
1401
+ top: 20px;
1402
+ right: 20px;
1403
+ z-index: 9999;
1404
+ min-width: 300px;
1405
+ max-width: 400px;
1406
+ `;
1407
+ notification.innerHTML = `
1408
+ ${message}
1409
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
1410
+ `;
1411
+
1412
+ document.body.appendChild(notification);
1413
+
1414
+ // Auto-remove after 3 seconds
1415
+ setTimeout(() => {
1416
+ if (notification.parentNode) {
1417
+ notification.remove();
1418
+ }
1419
+ }, 3000);
1420
+ }
1421
+
1422
+ // Track user activity
1423
+ function trackActivity(actionType, actionData = {}) {
1424
+ // Get or create session ID
1425
+ let sessionId = localStorage.getItem('userSessionId');
1426
+ if (!sessionId) {
1427
+ sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
1428
+ localStorage.setItem('userSessionId', sessionId);
1429
+ }
1430
+
1431
+ // Get current user tracking data
1432
+ let userTrackingData = JSON.parse(localStorage.getItem('userTrackingData') || '{}');
1433
+
1434
+ // Initialize tracking data structure if not exists
1435
+ if (!userTrackingData.clickedProperties) userTrackingData.clickedProperties = [];
1436
+ if (!userTrackingData.detailedViews) userTrackingData.detailedViews = [];
1437
+ if (!userTrackingData.searchHistory) userTrackingData.searchHistory = [];
1438
+ if (!userTrackingData.features) userTrackingData.features = [];
1439
+ if (!userTrackingData.priceRanges) userTrackingData.priceRanges = [];
1440
+ if (!userTrackingData.propertyTypes) userTrackingData.propertyTypes = [];
1441
+
1442
+ // Track based on action type
1443
+ switch (actionType) {
1444
+ case 'property_click':
1445
+ console.log('πŸ”΅ Property CLICK tracked:', actionData.property?.propertyName);
1446
+ showNotification(`βœ… Click tracked: ${actionData.property?.propertyName}`, 'success');
1447
+
1448
+ if (actionData.property) {
1449
+ // Check if this property is already in clickedProperties to avoid duplicates
1450
+ const existingClick = userTrackingData.clickedProperties.find(p => parseInt(p.id) === parseInt(actionData.property.id));
1451
+ if (!existingClick) {
1452
+ userTrackingData.clickedProperties.push({
1453
+ id: actionData.property.id,
1454
+ name: actionData.property.propertyName,
1455
+ type: actionData.property.typeName,
1456
+ price: actionData.property.marketValue,
1457
+ timestamp: new Date().toISOString()
1458
+ });
1459
+ console.log('βœ… Added to clickedProperties:', actionData.property.propertyName);
1460
+ } else {
1461
+ console.log('⚠️ Property already in clickedProperties:', actionData.property.propertyName);
1462
+ }
1463
+
1464
+ // Track property type (avoid duplicates)
1465
+ if (actionData.property.typeName) {
1466
+ if (!userTrackingData.propertyTypes.includes(actionData.property.typeName)) {
1467
+ userTrackingData.propertyTypes.push(actionData.property.typeName);
1468
+ }
1469
+ }
1470
+
1471
+ // Track price range (avoid duplicates)
1472
+ if (actionData.property.marketValue) {
1473
+ const price = actionData.property.marketValue;
1474
+ let priceRange = '';
1475
+ if (price <= 500000) priceRange = '0-500000';
1476
+ else if (price <= 1000000) priceRange = '500000-1000000';
1477
+ else if (price <= 2000000) priceRange = '1000000-2000000';
1478
+ else if (price <= 5000000) priceRange = '2000000-5000000';
1479
+ else if (price <= 10000000) priceRange = '5000000-10000000';
1480
+ else priceRange = '10000000+';
1481
+
1482
+ if (!userTrackingData.priceRanges.includes(priceRange)) {
1483
+ userTrackingData.priceRanges.push(priceRange);
1484
+ }
1485
+ }
1486
+ }
1487
+ break;
1488
+
1489
+ case 'click':
1490
+ if (actionData.property) {
1491
+ userTrackingData.clickedProperties.push({
1492
+ id: actionData.property.id,
1493
+ name: actionData.property.propertyName,
1494
+ type: actionData.property.typeName,
1495
+ price: actionData.property.marketValue,
1496
+ timestamp: new Date().toISOString()
1497
+ });
1498
+ }
1499
+ break;
1500
+
1501
+ case 'detailed_view':
1502
+ console.log('🟒 Property DETAILED VIEW tracked:', actionData.property?.propertyName, 'Duration:', actionData.duration, 'seconds');
1503
+ showNotification(`πŸ‘οΈ Detailed view tracked: ${actionData.property?.propertyName} (${actionData.duration}s)`, 'info');
1504
+
1505
+ if (actionData.property) {
1506
+ const existingView = userTrackingData.detailedViews.find(
1507
+ view => parseInt(view.propertyId) === parseInt(actionData.property.id)
1508
+ );
1509
+
1510
+ if (existingView) {
1511
+ existingView.totalDuration += actionData.duration || 0;
1512
+ existingView.viewCount += 1;
1513
+ existingView.lastViewed = new Date().toISOString();
1514
+ console.log('βœ… Updated existing detailed view:', actionData.property.propertyName, 'Duration:', actionData.duration, 'Total:', existingView.totalDuration);
1515
+ } else {
1516
+ userTrackingData.detailedViews.push({
1517
+ propertyId: actionData.property.id,
1518
+ propertyName: actionData.property.propertyName,
1519
+ propertyType: actionData.property.typeName,
1520
+ price: actionData.property.marketValue,
1521
+ totalDuration: actionData.duration || 0,
1522
+ viewCount: 1,
1523
+ lastViewed: new Date().toISOString()
1524
+ });
1525
+ console.log('βœ… Added new detailed view:', actionData.property.propertyName, 'Duration:', actionData.duration);
1526
+ }
1527
+ }
1528
+ break;
1529
+
1530
+ case 'search':
1531
+ userTrackingData.searchHistory.push({
1532
+ query: actionData.query || '',
1533
+ filters: actionData.filters || {},
1534
+ timestamp: new Date().toISOString()
1535
+ });
1536
+ break;
1537
+
1538
+ case 'feature_click':
1539
+ if (actionData.feature && !userTrackingData.features.includes(actionData.feature)) {
1540
+ userTrackingData.features.push(actionData.feature);
1541
+ }
1542
+ break;
1543
+
1544
+ case 'price_range_select':
1545
+ if (actionData.priceRange) {
1546
+ userTrackingData.priceRanges.push(actionData.priceRange);
1547
+ }
1548
+ break;
1549
+
1550
+ case 'property_type_select':
1551
+ if (actionData.propertyType) {
1552
+ userTrackingData.propertyTypes.push(actionData.propertyType);
1553
+ }
1554
+ break;
1555
+ }
1556
+
1557
+ // Save updated tracking data
1558
+ localStorage.setItem('userTrackingData', JSON.stringify(userTrackingData));
1559
+
1560
+ // Send to server
1561
+ fetch('/api/track', {
1562
+ method: 'POST',
1563
+ headers: {
1564
+ 'Content-Type': 'application/json',
1565
+ },
1566
+ body: JSON.stringify({
1567
+ session_id: sessionId,
1568
+ action_type: actionType,
1569
+ action_data: actionData,
1570
+ user_id: sessionId,
1571
+ email: userEmail
1572
+ })
1573
+ }).catch(error => {
1574
+ console.error('Error tracking activity:', error);
1575
+ });
1576
+ }
1577
+
1578
+ // Enhanced property click tracking
1579
+ function trackPropertyClick(property) {
1580
+ trackActivity('click', { property: property });
1581
+
1582
+ // Track property type preference
1583
+ if (property.typeName) {
1584
+ trackActivity('property_type_select', { propertyType: property.typeName });
1585
+ }
1586
+
1587
+ // Track price range preference
1588
+ if (property.marketValue) {
1589
+ const priceRange = getPriceRange(property.marketValue);
1590
+ trackActivity('price_range_select', { priceRange: priceRange });
1591
+ }
1592
+ }
1593
+
1594
+ // Track detailed view with duration
1595
+ function trackDetailedView(property, duration = 0) {
1596
+ trackActivity('detailed_view', {
1597
+ property: property,
1598
+ duration: duration
1599
+ });
1600
+ }
1601
+
1602
+ // Track search activity
1603
+ function trackSearch(query, filters = {}) {
1604
+ trackActivity('search', {
1605
+ query: query,
1606
+ filters: filters
1607
+ });
1608
+ }
1609
+
1610
+ // Track feature interest
1611
+ function trackFeatureInterest(feature) {
1612
+ trackActivity('feature_click', { feature: feature });
1613
+ }
1614
+
1615
+ // Get price range category
1616
+ function getPriceRange(price) {
1617
+ if (price <= 500000) return '0-500000';
1618
+ if (price <= 1000000) return '500000-1000000';
1619
+ if (price <= 2000000) return '1000000-2000000';
1620
+ if (price <= 5000000) return '2000000-5000000';
1621
+ if (price <= 10000000) return '5000000-10000000';
1622
+ return '10000000+';
1623
+ }
1624
+
1625
+ // Get user tracking data
1626
+ function getUserTrackingData() {
1627
+ return JSON.parse(localStorage.getItem('userTrackingData') || '{}');
1628
+ }
1629
+
1630
+ // Clear user tracking data
1631
+ function clearUserTrackingData() {
1632
+ localStorage.removeItem('userTrackingData');
1633
+ localStorage.removeItem('userSessionId');
1634
+ console.log('User tracking data cleared');
1635
+ }
1636
+
1637
+ // Export user tracking data
1638
+ function exportUserTrackingData() {
1639
+ const data = getUserTrackingData();
1640
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
1641
+ const url = URL.createObjectURL(blob);
1642
+ const a = document.createElement('a');
1643
+ a.href = url;
1644
+ a.download = 'user_tracking_data.json';
1645
+ document.body.appendChild(a);
1646
+ a.click();
1647
+ document.body.removeChild(a);
1648
+ URL.revokeObjectURL(url);
1649
+ }
1650
+
1651
+ // Enhanced property card creation with tracking
1652
+ function createPropertyCard(property) {
1653
+ const card = document.createElement('div');
1654
+ card.className = 'property-card';
1655
+ card.onclick = () => viewProperty(property);
1656
+
1657
+ const features = property.features || [];
1658
+ // Use property images if available, otherwise show no image
1659
+ const images = property.propertyImages || [];
1660
+
1661
+ card.innerHTML = `
1662
+ <div class="position-relative">
1663
+ ${images.length > 0 ? `
1664
+ <img src="${images[0]}"
1665
+ alt="${property.propertyName}"
1666
+ class="property-image"
1667
+ onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
1668
+ ` : ''}
1669
+ <div class="property-badge">${property.typeName || 'Property'}</div>
1670
+ ${images.length === 0 ? `
1671
+ <div class="no-image-placeholder" style="width: 100%; height: 250px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 18px;">
1672
+ <i class="fas fa-home me-2"></i>${property.propertyName || 'Property'}
1673
+ </div>
1674
+ ` : ''}
1675
+ </div>
1676
+ <div class="property-content">
1677
+ <h5 class="property-title">${property.propertyName || 'Property'}</h5>
1678
+ <div class="property-price">β‚Ή${(property.marketValue || 0).toLocaleString()}</div>
1679
+ <div class="property-location">
1680
+ <i class="fas fa-map-marker-alt me-2"></i>
1681
+ ${property.address || 'Location not specified'}
1682
+ </div>
1683
+ <div class="property-features">
1684
+ ${features.slice(0, 3).map(feature =>
1685
+ `<span class="feature-tag available">${feature}</span>`
1686
+ ).join('')}
1687
+ ${features.length > 3 ? `<span class="feature-tag">+${features.length - 3} more</span>` : ''}
1688
+ </div>
1689
+ <div class="property-actions">
1690
+ <button class="btn-action btn-primary-custom" onclick="event.stopPropagation(); scheduleVisit('${property.id}', '${property.propertyName}', '${property.marketValue}', '${property.typeName}', '${property.address}')">
1691
+ <i class="fas fa-calendar me-2"></i>Schedule Visit
1692
+ </button>
1693
+ <button class="btn-action btn-outline-custom" onclick="event.stopPropagation(); contactAgent('${property.id}')">
1694
+ <i class="fas fa-phone me-2"></i>Contact
1695
+ </button>
1696
+ </div>
1697
+ </div>
1698
+ `;
1699
+
1700
+ return card;
1701
+ }
1702
+
1703
+ // Enhanced visit request with tracking
1704
+ function requestVisit(propertyId) {
1705
+ const property = allProperties.find(p => p.id === propertyId);
1706
+ if (!property) return;
1707
+
1708
+ // Track visit request
1709
+ trackActivity('visit_request', {
1710
+ property: property,
1711
+ action: 'visit_request_initiated'
1712
+ });
1713
+
1714
+ // Show visit request modal
1715
+ showVisitRequestModal(property);
1716
+ }
1717
+
1718
+ // Enhanced view details with tracking
1719
+ function viewDetails(propertyId) {
1720
+ const property = allProperties.find(p => p.id === propertyId);
1721
+ if (!property) return;
1722
+
1723
+ // Track detailed view with default duration
1724
+ trackDetailedView(property, 10); // Assume 10 seconds for button click
1725
+
1726
+ // Show property details modal
1727
+ showPropertyDetailsModal(property);
1728
+ }
1729
+
1730
+ // Property details modal with duration tracking
1731
+ let propertyModalOpenTime = null;
1732
+ let currentPropertyInModal = null;
1733
+
1734
+ function showPropertyDetailsModal(property) {
1735
+ console.log('🟒 Opening modal for:', property.propertyName);
1736
+ console.log('🟒 Property data:', property);
1737
+
1738
+ currentPropertyInModal = property;
1739
+ propertyModalOpenTime = Date.now();
1740
+
1741
+ // Set modal content
1742
+ document.getElementById('detailPropertyName').textContent = property.propertyName || 'Property';
1743
+ document.getElementById('detailPropertyPrice').textContent = `β‚Ή${parseInt(property.marketValue || 0).toLocaleString()}`;
1744
+ document.getElementById('detailPropertyType').textContent = property.typeName || 'Property';
1745
+ document.getElementById('detailPropertyAddress').textContent = property.address || 'Location not specified';
1746
+ document.getElementById('detailPropertyBeds').textContent = property.beds || 'N/A';
1747
+ document.getElementById('detailPropertyBaths').textContent = property.baths || 'N/A';
1748
+ document.getElementById('detailPropertySize').textContent = `${property.totalSquareFeet || 0} sq ft`;
1749
+
1750
+ // Load property images
1751
+ const imagesContainer = document.getElementById('propertyImages');
1752
+ // Use property images if available
1753
+ const validImages = property.propertyImages || [];
1754
+
1755
+ if (validImages && validImages.length > 0) {
1756
+ imagesContainer.innerHTML = `
1757
+ <div id="propertyImageCarousel" class="carousel slide" data-bs-ride="carousel">
1758
+ <div class="carousel-inner">
1759
+ ${validImages.map((img, index) => `
1760
+ <div class="carousel-item ${index === 0 ? 'active' : ''}">
1761
+ <img src="${img}" class="d-block w-100" style="height: 400px; object-fit: cover;" alt="Property Image"
1762
+ onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
1763
+ <div class="text-center py-5" style="display: none; height: 400px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white;">
1764
+ <div>
1765
+ <i class="fas fa-image fa-3x mb-3"></i>
1766
+ <p class="mb-0">Image not available</p>
1767
+ </div>
1768
+ </div>
1769
+ </div>
1770
+ `).join('')}
1771
+ </div>
1772
+ ${validImages.length > 1 ? `
1773
+ <button class="carousel-control-prev" type="button" data-bs-target="#propertyImageCarousel" data-bs-slide="prev">
1774
+ <span class="carousel-control-prev-icon" aria-hidden="true"></span>
1775
+ <span class="visually-hidden">Previous</span>
1776
+ </button>
1777
+ <button class="carousel-control-next" type="button" data-bs-target="#propertyImageCarousel" data-bs-slide="next">
1778
+ <span class="carousel-control-next-icon" aria-hidden="true"></span>
1779
+ <span class="visually-hidden">Next</span>
1780
+ </button>
1781
+ ` : ''}
1782
+ </div>
1783
+ `;
1784
+ } else {
1785
+ imagesContainer.innerHTML = `
1786
+ <div class="text-center py-5">
1787
+ <i class="fas fa-image fa-3x text-muted mb-3"></i>
1788
+ <p class="text-muted">No images available</p>
1789
+ </div>
1790
+ `;
1791
+ }
1792
+
1793
+ // Load property description
1794
+ const descriptionContainer = document.getElementById('propertyDescription');
1795
+ descriptionContainer.innerHTML = `
1796
+ <h5>Description</h5>
1797
+ <p>${property.description || 'No description available'}</p>
1798
+ `;
1799
+
1800
+ // Load property features
1801
+ const featuresContainer = document.getElementById('detailPropertyFeatures');
1802
+ if (property.features && Object.keys(property.features).length > 0) {
1803
+ const featuresList = Object.keys(property.features).map(feature =>
1804
+ `<span class="badge bg-primary me-2 mb-2">${feature}</span>`
1805
+ ).join('');
1806
+ featuresContainer.innerHTML = featuresList;
1807
+ } else {
1808
+ featuresContainer.innerHTML = '<p class="text-muted">No features listed</p>';
1809
+ }
1810
+
1811
+ // Track modal open
1812
+ trackActivity('property_details_modal_open', {
1813
+ property_id: property.id,
1814
+ property_name: property.propertyName,
1815
+ property_type: property.typeName,
1816
+ property_price: property.marketValue,
1817
+ timestamp: new Date().toISOString()
1818
+ });
1819
+
1820
+ // Show modal
1821
+ const modalElement = document.getElementById('propertyDetailsModal');
1822
+ if (modalElement) {
1823
+ console.log('🟒 Modal element found:', modalElement);
1824
+ const propertyModal = new bootstrap.Modal(modalElement);
1825
+ propertyModal.show();
1826
+ console.log('βœ… Modal should now be visible');
1827
+
1828
+ // Show notification that modal opened
1829
+ showNotification(`πŸ” Modal opened: ${property.propertyName}`, 'info');
1830
+ } else {
1831
+ console.error('❌ Modal element not found!');
1832
+ showNotification('❌ Modal element not found! Check console for details.', 'danger');
1833
+ console.log('🟒 Available elements with "modal" in ID:');
1834
+ document.querySelectorAll('[id*="modal"]').forEach(el => {
1835
+ console.log(' -', el.id);
1836
+ });
1837
+ }
1838
+
1839
+ // Track modal duration when closed
1840
+ document.getElementById('propertyDetailsModal').addEventListener('hidden.bs.modal', function() {
1841
+ if (propertyModalOpenTime && currentPropertyInModal) {
1842
+ const duration = Date.now() - propertyModalOpenTime;
1843
+ const durationSeconds = Math.round(duration / 1000);
1844
+
1845
+ // Track detailed view with actual duration
1846
+ trackActivity('detailed_view', {
1847
+ property: currentPropertyInModal,
1848
+ duration: durationSeconds
1849
+ });
1850
+
1851
+ // Track modal close
1852
+ trackActivity('property_details_modal_close', {
1853
+ property_id: currentPropertyInModal.id,
1854
+ property_name: currentPropertyInModal.propertyName,
1855
+ duration_seconds: durationSeconds,
1856
+ timestamp: new Date().toISOString()
1857
+ });
1858
+
1859
+ console.log(`Property details viewed: ${currentPropertyInModal.propertyName} for ${durationSeconds} seconds`);
1860
+
1861
+ propertyModalOpenTime = null;
1862
+ currentPropertyInModal = null;
1863
+ }
1864
+ }, { once: true }); // Use once: true to prevent multiple event listeners
1865
+ }
1866
+
1867
+ function openVisitModalFromDetails() {
1868
+ if (currentPropertyInModal) {
1869
+ // Close property details modal
1870
+ const propertyModal = bootstrap.Modal.getInstance(document.getElementById('propertyDetailsModal'));
1871
+ propertyModal.hide();
1872
+
1873
+ // Open visit modal with current property
1874
+ openVisitModal(
1875
+ currentPropertyInModal.id,
1876
+ currentPropertyInModal.propertyName,
1877
+ currentPropertyInModal.marketValue,
1878
+ currentPropertyInModal.typeName,
1879
+ currentPropertyInModal.address
1880
+ );
1881
+ }
1882
+ }
1883
+
1884
+ function addToFavorites() {
1885
+ if (currentPropertyInModal) {
1886
+ // Add to favorites logic
1887
+ const favorites = JSON.parse(localStorage.getItem('favorites') || '[]');
1888
+ const existingIndex = favorites.findIndex(f => f.id === currentPropertyInModal.id);
1889
+
1890
+ if (existingIndex === -1) {
1891
+ favorites.push(currentPropertyInModal);
1892
+ localStorage.setItem('favorites', JSON.stringify(favorites));
1893
+ alert('Added to favorites!');
1894
+
1895
+ // Track favorite action
1896
+ trackActivity('property_added_to_favorites', {
1897
+ property_id: currentPropertyInModal.id,
1898
+ property_name: currentPropertyInModal.propertyName
1899
+ });
1900
+ } else {
1901
+ alert('Already in favorites!');
1902
+ }
1903
+ }
1904
+ }
1905
+
1906
+ function shareProperty() {
1907
+ if (currentPropertyInModal) {
1908
+ // Share property logic
1909
+ const shareText = `Check out this property: ${currentPropertyInModal.propertyName} - β‚Ή${parseInt(currentPropertyInModal.marketValue || 0).toLocaleString()}`;
1910
+
1911
+ if (navigator.share) {
1912
+ navigator.share({
1913
+ title: currentPropertyInModal.propertyName,
1914
+ text: shareText,
1915
+ url: window.location.href
1916
+ });
1917
+ } else {
1918
+ // Fallback: copy to clipboard
1919
+ navigator.clipboard.writeText(shareText).then(() => {
1920
+ alert('Property link copied to clipboard!');
1921
+ });
1922
+ }
1923
+
1924
+ // Track share action
1925
+ trackActivity('property_shared', {
1926
+ property_id: currentPropertyInModal.id,
1927
+ property_name: currentPropertyInModal.propertyName
1928
+ });
1929
+ }
1930
+ }
1931
+
1932
+ // Track scroll depth with enhanced analytics
1933
+ let maxScrollDepth = 0;
1934
+ let scrollStartTime = Date.now();
1935
+
1936
+ window.addEventListener('scroll', () => {
1937
+ const scrollDepth = Math.round((window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100);
1938
+ if (scrollDepth > maxScrollDepth) {
1939
+ maxScrollDepth = scrollDepth;
1940
+
1941
+ // Track scroll depth every 10%
1942
+ if (scrollDepth % 10 === 0) {
1943
+ trackActivity('scroll_depth', {
1944
+ depth: scrollDepth,
1945
+ max_depth: maxScrollDepth,
1946
+ time_spent: Math.round((Date.now() - scrollStartTime) / 1000)
1947
+ });
1948
+ }
1949
+ }
1950
+ });
1951
+
1952
+ // Track time spent with enhanced analytics
1953
+ let startTime = Date.now();
1954
+ let lastTrackedTime = 0;
1955
+
1956
+ setInterval(() => {
1957
+ const timeSpent = Math.round((Date.now() - startTime) / 1000);
1958
+
1959
+ // Track every minute
1960
+ if (timeSpent >= lastTrackedTime + 60) {
1961
+ trackActivity('time_spent', {
1962
+ duration: timeSpent,
1963
+ session_duration: timeSpent
1964
+ });
1965
+ lastTrackedTime = timeSpent;
1966
+ }
1967
+ }, 60000);
1968
+
1969
+ // Track page load and session start
1970
+ document.addEventListener('DOMContentLoaded', () => {
1971
+ trackActivity('page_load', {
1972
+ page: 'home',
1973
+ timestamp: new Date().toISOString()
1974
+ });
1975
+ });
1976
+
1977
+ // Track search functionality
1978
+ function performSearch() {
1979
+ const searchQuery = document.getElementById('searchInput').value;
1980
+ const propertyType = document.getElementById('propertyTypeFilter').value;
1981
+ const minPrice = document.getElementById('minPriceFilter').value;
1982
+ const maxPrice = document.getElementById('maxPriceFilter').value;
1983
+
1984
+ const filters = {
1985
+ propertyType: propertyType,
1986
+ minPrice: minPrice,
1987
+ maxPrice: maxPrice
1988
+ };
1989
+
1990
+ // Track search activity
1991
+ trackSearch(searchQuery, filters);
1992
+
1993
+ // Perform search
1994
+ filterProperties();
1995
+ }
1996
+
1997
+ // Enhanced email form submission with tracking
1998
+ document.getElementById('emailForm').addEventListener('submit', async function(e) {
1999
+ e.preventDefault();
2000
+
2001
+ const email = document.getElementById('userEmail').value;
2002
+ userEmail = email;
2003
+
2004
+ // Track email submission
2005
+ trackActivity('email_submit', {
2006
+ email: email,
2007
+ timestamp: new Date().toISOString()
2008
+ });
2009
+
2010
+ try {
2011
+ const userTrackingData = getUserTrackingData();
2012
+ const sessionId = localStorage.getItem('userSessionId');
2013
+
2014
+ const response = await fetch('/api/send-recommendations', {
2015
+ method: 'POST',
2016
+ headers: {
2017
+ 'Content-Type': 'application/json',
2018
+ },
2019
+ body: JSON.stringify({
2020
+ email: email,
2021
+ session_id: sessionId,
2022
+ user_tracking_data: userTrackingData
2023
+ })
2024
+ });
2025
+
2026
+ const data = await response.json();
2027
+
2028
+ if (data.success) {
2029
+ alert('Recommendations sent to your email!');
2030
+ hideFloatingEmail();
2031
+
2032
+ // Track successful email send
2033
+ trackActivity('email_sent_success', {
2034
+ email: email,
2035
+ recommendations_count: data.recommendations?.length || 0
2036
+ });
2037
+
2038
+ // Load recommendations
2039
+ loadRecommendations();
2040
+ } else {
2041
+ alert('Failed to send recommendations. Please try again.');
2042
+
2043
+ // Track failed email send
2044
+ trackActivity('email_sent_failed', {
2045
+ email: email,
2046
+ error: data.error
2047
+ });
2048
+ }
2049
+
2050
+ } catch (error) {
2051
+ console.error('Error sending recommendations:', error);
2052
+ alert('Failed to send recommendations. Please try again.');
2053
+
2054
+ // Track error
2055
+ trackActivity('email_sent_error', {
2056
+ email: email,
2057
+ error: error.message
2058
+ });
2059
+ }
2060
+ });
2061
+
2062
+ // Enhanced recommendations loading with tracking
2063
+ async function loadRecommendations() {
2064
+ try {
2065
+ const sessionId = localStorage.getItem('userSessionId');
2066
+ const userTrackingData = getUserTrackingData();
2067
+
2068
+ const params = new URLSearchParams({
2069
+ email: userEmail,
2070
+ session_id: sessionId
2071
+ });
2072
+
2073
+ const response = await fetch(`/api/recommendations?${params}`, {
2074
+ method: 'POST',
2075
+ headers: {
2076
+ 'Content-Type': 'application/json',
2077
+ },
2078
+ body: JSON.stringify({
2079
+ user_tracking_data: userTrackingData
2080
+ })
2081
+ });
2082
+
2083
+ const data = await response.json();
2084
+
2085
+ if (data.success && data.recommendations) {
2086
+ renderRecommendations(data.recommendations);
2087
+ updateStats(data.user_preferences);
2088
+
2089
+ // Track recommendations loaded
2090
+ trackActivity('recommendations_loaded', {
2091
+ count: data.recommendations.length,
2092
+ user_preferences: data.user_preferences
2093
+ });
2094
+ }
2095
+
2096
+ } catch (error) {
2097
+ console.error('Error loading recommendations:', error);
2098
+
2099
+ // Track error
2100
+ trackActivity('recommendations_error', {
2101
+ error: error.message
2102
+ });
2103
+ }
2104
+ }
2105
+
2106
+ // Enhanced stats update with tracking data
2107
+ function updateStats(preferences) {
2108
+ const userTrackingData = getUserTrackingData();
2109
+
2110
+ document.getElementById('propertiesViewed').textContent = preferences.total_properties_viewed || 0;
2111
+ document.getElementById('timeSpent').textContent = Math.round((preferences.total_time_spent || 0) / 60);
2112
+
2113
+ // Update visit requests count
2114
+ const visitHistory = getVisitHistory();
2115
+ document.getElementById('visitRequests').textContent = visitHistory.length;
2116
+
2117
+ // Get lead score with tracking
2118
+ if (userEmail) {
2119
+ fetch(`/api/lead-qualification?email=${userEmail}`)
2120
+ .then(response => response.json())
2121
+ .then(data => {
2122
+ document.getElementById('leadScore').textContent = data.lead_score || 0;
2123
+
2124
+ // Track lead score update
2125
+ trackActivity('lead_score_updated', {
2126
+ score: data.lead_score,
2127
+ status: data.lead_status
2128
+ });
2129
+ })
2130
+ .catch(error => {
2131
+ console.error('Error getting lead score:', error);
2132
+ });
2133
+ }
2134
+
2135
+ // Track stats update
2136
+ trackActivity('stats_updated', {
2137
+ properties_viewed: preferences.total_properties_viewed,
2138
+ time_spent: preferences.total_time_spent,
2139
+ visit_requests: visitHistory.length
2140
+ });
2141
+ }
2142
+
2143
+ function updateVisitStats(history, attempts) {
2144
+ // Update visit requests count
2145
+ document.getElementById('visitRequests').textContent = history.length;
2146
+
2147
+ // Calculate average modal duration
2148
+ const interactions = getModalInteractions();
2149
+ const avgDuration = interactions.length > 0
2150
+ ? Math.round(interactions.reduce((sum, i) => sum + i.duration, 0) / interactions.length / 1000)
2151
+ : 0;
2152
+
2153
+ // Track visit analytics
2154
+ trackActivity('visit_analytics_updated', {
2155
+ total_requests: history.length,
2156
+ total_attempts: attempts.length,
2157
+ total_interactions: interactions.length,
2158
+ average_modal_duration: avgDuration
2159
+ });
2160
+
2161
+ // Show visit analytics in console for debugging
2162
+ console.log('Visit Analytics:', {
2163
+ totalRequests: history.length,
2164
+ totalAttempts: attempts.length,
2165
+ totalInteractions: interactions.length,
2166
+ averageModalDuration: avgDuration + ' seconds'
2167
+ });
2168
+ }
2169
+
2170
+ // Add automatic detailed view tracking for property cards
2171
+ function addDetailedViewTracking() {
2172
+ const propertyCards = document.querySelectorAll('.property-card');
2173
+ propertyCards.forEach(card => {
2174
+ let viewStartTime = null;
2175
+
2176
+ card.addEventListener('mouseenter', () => {
2177
+ viewStartTime = Date.now();
2178
+ });
2179
+
2180
+ card.addEventListener('mouseleave', () => {
2181
+ if (viewStartTime) {
2182
+ const duration = Math.round((Date.now() - viewStartTime) / 1000);
2183
+ if (duration >= 2) { // Track if viewed for at least 2 seconds
2184
+ const propertyId = card.getAttribute('data-property-id');
2185
+ const property = allProperties.find(p => p.id == propertyId);
2186
+ if (property) {
2187
+ trackDetailedView(property, duration);
2188
+ console.log(`Auto-tracked detailed view: ${property.propertyName} for ${duration}s`);
2189
+ }
2190
+ }
2191
+ viewStartTime = null;
2192
+ }
2193
+ });
2194
+ });
2195
+ }
2196
+
2197
+ // Call this function after properties are loaded
2198
+ function initializeDetailedViewTracking() {
2199
+ setTimeout(() => {
2200
+ addDetailedViewTracking();
2201
+ }, 1000); // Wait for properties to load
2202
+ }
2203
+
2204
+ // Test functions for debugging
2205
+ function testPropertyClick() {
2206
+ console.log('πŸ§ͺ Testing property click...');
2207
+
2208
+ // Create a test property
2209
+ const testProperty = {
2210
+ id: 999,
2211
+ propertyName: 'Test Property',
2212
+ typeName: 'Villa',
2213
+ marketValue: 5000000,
2214
+ beds: 3,
2215
+ baths: 2,
2216
+ totalSquareFeet: 2000,
2217
+ numberOfRooms: 4,
2218
+ address: 'Test Address, Test City',
2219
+ description: 'This is a test property for debugging purposes.',
2220
+ propertyImages: [],
2221
+ features: ['parking', 'garden', 'security']
2222
+ };
2223
+
2224
+ console.log('πŸ§ͺ Test property created:', testProperty);
2225
+
2226
+ // Simulate property click
2227
+ viewProperty(testProperty);
2228
+
2229
+ console.log('πŸ§ͺ Test property click completed');
2230
+ }
2231
+
2232
+ function testModal() {
2233
+ console.log('πŸ§ͺ Testing modal...');
2234
+ const testProperty = {
2235
+ id: 888,
2236
+ propertyName: 'Test Modal Property',
2237
+ typeName: 'Apartment',
2238
+ marketValue: 3000000,
2239
+ beds: 2,
2240
+ baths: 2,
2241
+ totalSquareFeet: 1200,
2242
+ numberOfRooms: 3,
2243
+ address: 'Test Modal Address',
2244
+ description: 'This is a test modal property.',
2245
+ propertyImages: [],
2246
+ features: ['lift', 'security', 'parking']
2247
+ };
2248
+
2249
+ showPropertyDetailsModal(testProperty);
2250
+ }
2251
+
2252
+ function showTrackingData() {
2253
+ const userTrackingData = getUserTrackingData();
2254
+ console.log('πŸ“Š Current Tracking Data:');
2255
+ console.log('Clicked Properties:', userTrackingData.clickedProperties);
2256
+ console.log('Detailed Views:', userTrackingData.detailedViews);
2257
+ console.log('Property Types:', userTrackingData.propertyTypes);
2258
+ console.log('Price Ranges:', userTrackingData.priceRanges);
2259
+ console.log('Full Data:', userTrackingData);
2260
+
2261
+ showNotification(`πŸ“Š Clicked: ${userTrackingData.clickedProperties?.length || 0}, Views: ${userTrackingData.detailedViews?.length || 0}`, 'info');
2262
+ }
2263
+
2264
+ function clearTrackingData() {
2265
+ localStorage.removeItem('userTrackingData');
2266
+ localStorage.removeItem('userSessionId');
2267
+ console.log('βœ… Tracking data cleared');
2268
+ showNotification('βœ… Tracking data cleared', 'success');
2269
+ }
2270
+ </script>
2271
+ </body>
2272
+ </html>
templates/lead_analysis.html ADDED
@@ -0,0 +1,796 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Lead Analysis Dashboard</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/date-fns@2.29.3/index.min.js"></script>
9
+ <style>
10
+ * {
11
+ margin: 0;
12
+ padding: 0;
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ body {
17
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
18
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
19
+ min-height: 100vh;
20
+ color: #333;
21
+ }
22
+
23
+ .container {
24
+ max-width: 1400px;
25
+ margin: 0 auto;
26
+ padding: 20px;
27
+ }
28
+
29
+ .header {
30
+ text-align: center;
31
+ color: white;
32
+ margin-bottom: 30px;
33
+ }
34
+
35
+ .header h1 {
36
+ font-size: 2.5em;
37
+ margin-bottom: 10px;
38
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
39
+ }
40
+
41
+ .header p {
42
+ font-size: 1.1em;
43
+ opacity: 0.9;
44
+ }
45
+
46
+ .search-section {
47
+ background: white;
48
+ border-radius: 15px;
49
+ padding: 30px;
50
+ margin-bottom: 30px;
51
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
52
+ }
53
+
54
+ .search-form {
55
+ display: flex;
56
+ gap: 15px;
57
+ align-items: end;
58
+ justify-content: center;
59
+ flex-wrap: wrap;
60
+ }
61
+
62
+ .input-group {
63
+ display: flex;
64
+ flex-direction: column;
65
+ min-width: 200px;
66
+ }
67
+
68
+ .input-group label {
69
+ font-weight: 600;
70
+ margin-bottom: 5px;
71
+ color: #555;
72
+ }
73
+
74
+ .input-group input {
75
+ padding: 12px 15px;
76
+ border: 2px solid #e0e0e0;
77
+ border-radius: 8px;
78
+ font-size: 16px;
79
+ transition: all 0.3s ease;
80
+ }
81
+
82
+ .input-group input:focus {
83
+ outline: none;
84
+ border-color: #667eea;
85
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
86
+ }
87
+
88
+ .btn {
89
+ padding: 12px 25px;
90
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
91
+ color: white;
92
+ border: none;
93
+ border-radius: 8px;
94
+ font-size: 16px;
95
+ font-weight: 600;
96
+ cursor: pointer;
97
+ transition: all 0.3s ease;
98
+ }
99
+
100
+ .btn:hover {
101
+ transform: translateY(-2px);
102
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
103
+ }
104
+
105
+ .btn:disabled {
106
+ opacity: 0.6;
107
+ cursor: not-allowed;
108
+ transform: none;
109
+ }
110
+
111
+ .loading {
112
+ text-align: center;
113
+ padding: 40px;
114
+ color: white;
115
+ font-size: 18px;
116
+ }
117
+
118
+ .error {
119
+ background: #ff6b6b;
120
+ color: white;
121
+ padding: 20px;
122
+ border-radius: 10px;
123
+ margin: 20px 0;
124
+ text-align: center;
125
+ }
126
+
127
+ .results-section {
128
+ display: none;
129
+ }
130
+
131
+ .lead-status {
132
+ background: white;
133
+ border-radius: 15px;
134
+ padding: 25px;
135
+ margin-bottom: 20px;
136
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
137
+ }
138
+
139
+ .status-header {
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: space-between;
143
+ margin-bottom: 20px;
144
+ flex-wrap: wrap;
145
+ gap: 15px;
146
+ }
147
+
148
+ .status-badge {
149
+ padding: 10px 20px;
150
+ border-radius: 25px;
151
+ font-weight: bold;
152
+ font-size: 16px;
153
+ text-transform: uppercase;
154
+ letter-spacing: 1px;
155
+ }
156
+
157
+ .lead-score {
158
+ text-align: center;
159
+ }
160
+
161
+ .score-circle {
162
+ width: 120px;
163
+ height: 120px;
164
+ border-radius: 50%;
165
+ display: flex;
166
+ align-items: center;
167
+ justify-content: center;
168
+ margin: 0 auto 10px;
169
+ font-size: 24px;
170
+ font-weight: bold;
171
+ color: white;
172
+ background: conic-gradient(#667eea 0deg, #764ba2 360deg);
173
+ }
174
+
175
+ .analytics-grid {
176
+ display: grid;
177
+ grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
178
+ gap: 20px;
179
+ margin-bottom: 20px;
180
+ }
181
+
182
+ .analytics-card {
183
+ background: white;
184
+ border-radius: 15px;
185
+ padding: 25px;
186
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
187
+ }
188
+
189
+ .card-header {
190
+ display: flex;
191
+ align-items: center;
192
+ margin-bottom: 20px;
193
+ }
194
+
195
+ .card-icon {
196
+ width: 40px;
197
+ height: 40px;
198
+ border-radius: 10px;
199
+ display: flex;
200
+ align-items: center;
201
+ justify-content: center;
202
+ margin-right: 15px;
203
+ font-size: 20px;
204
+ color: white;
205
+ }
206
+
207
+ .card-title {
208
+ font-size: 18px;
209
+ font-weight: 600;
210
+ color: #333;
211
+ }
212
+
213
+ .metric-item {
214
+ display: flex;
215
+ justify-content: space-between;
216
+ align-items: center;
217
+ padding: 10px 0;
218
+ border-bottom: 1px solid #f0f0f0;
219
+ }
220
+
221
+ .metric-item:last-child {
222
+ border-bottom: none;
223
+ }
224
+
225
+ .metric-label {
226
+ color: #666;
227
+ font-weight: 500;
228
+ }
229
+
230
+ .metric-value {
231
+ font-weight: 600;
232
+ color: #333;
233
+ }
234
+
235
+ .properties-section {
236
+ background: white;
237
+ border-radius: 15px;
238
+ padding: 25px;
239
+ margin-bottom: 20px;
240
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
241
+ }
242
+
243
+ .properties-grid {
244
+ display: grid;
245
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
246
+ gap: 15px;
247
+ margin-top: 20px;
248
+ }
249
+
250
+ .property-card {
251
+ border: 2px solid #f0f0f0;
252
+ border-radius: 10px;
253
+ padding: 20px;
254
+ transition: all 0.3s ease;
255
+ }
256
+
257
+ .property-card:hover {
258
+ border-color: #667eea;
259
+ transform: translateY(-2px);
260
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
261
+ }
262
+
263
+ .property-name {
264
+ font-weight: 600;
265
+ color: #333;
266
+ margin-bottom: 10px;
267
+ }
268
+
269
+ .property-price {
270
+ font-size: 18px;
271
+ font-weight: bold;
272
+ color: #667eea;
273
+ margin-bottom: 10px;
274
+ }
275
+
276
+ .property-stats {
277
+ display: flex;
278
+ justify-content: space-between;
279
+ font-size: 14px;
280
+ color: #666;
281
+ }
282
+
283
+ .recommendations {
284
+ background: white;
285
+ border-radius: 15px;
286
+ padding: 25px;
287
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
288
+ }
289
+
290
+ .recommendation-item {
291
+ background: #f8f9ff;
292
+ border-left: 4px solid #667eea;
293
+ padding: 15px;
294
+ margin-bottom: 10px;
295
+ border-radius: 5px;
296
+ }
297
+
298
+ .chart-container {
299
+ position: relative;
300
+ height: 300px;
301
+ margin-top: 20px;
302
+ }
303
+
304
+ @media (max-width: 768px) {
305
+ .search-form {
306
+ flex-direction: column;
307
+ align-items: stretch;
308
+ }
309
+
310
+ .input-group {
311
+ min-width: auto;
312
+ }
313
+
314
+ .status-header {
315
+ flex-direction: column;
316
+ text-align: center;
317
+ }
318
+
319
+ .analytics-grid {
320
+ grid-template-columns: 1fr;
321
+ }
322
+ }
323
+
324
+ .fade-in {
325
+ animation: fadeIn 0.6s ease-in-out;
326
+ }
327
+
328
+ @keyframes fadeIn {
329
+ from { opacity: 0; transform: translateY(20px); }
330
+ to { opacity: 1; transform: translateY(0); }
331
+ }
332
+ </style>
333
+ </head>
334
+ <body>
335
+ <div class="container">
336
+ <div class="header">
337
+ <h1>🎯 Lead Analysis Dashboard</h1>
338
+ <p>Comprehensive Customer Behavior Analytics & Lead Qualification</p>
339
+ </div>
340
+
341
+ <div class="search-section">
342
+ <form class="search-form" id="searchForm">
343
+ <div class="input-group">
344
+ <label for="customerId">Customer ID</label>
345
+ <input type="number" id="customerId" name="customerId" placeholder="Enter Customer ID" required
346
+ value="{{ customer_id if customer_id else '' }}">
347
+ </div>
348
+ <button type="submit" class="btn" id="searchBtn">
349
+ πŸ” Analyze Customer
350
+ </button>
351
+ </form>
352
+ </div>
353
+
354
+ <div id="loadingSection" class="loading" style="display: none;">
355
+ <div>πŸ”„ Analyzing customer data...</div>
356
+ <div style="margin-top: 10px; font-size: 14px; opacity: 0.8;">
357
+ Fetching lead qualification and property analytics
358
+ </div>
359
+ </div>
360
+
361
+ <div id="errorSection" class="error" style="display: none;"></div>
362
+
363
+ <div id="resultsSection" class="results-section">
364
+ <!-- Lead Status Section -->
365
+ <div class="lead-status fade-in">
366
+ <div class="status-header">
367
+ <div>
368
+ <h2>Lead Qualification Status</h2>
369
+ <div id="statusBadge" class="status-badge"></div>
370
+ <p id="statusDescription" style="margin-top: 10px; color: #666;"></p>
371
+ </div>
372
+ <div class="lead-score">
373
+ <div id="scoreCircle" class="score-circle"></div>
374
+ <div>Lead Score</div>
375
+ </div>
376
+ </div>
377
+ </div>
378
+
379
+ <!-- Analytics Grid -->
380
+ <div class="analytics-grid fade-in">
381
+ <!-- Engagement Analytics -->
382
+ <div class="analytics-card">
383
+ <div class="card-header">
384
+ <div class="card-icon" style="background: #667eea;">πŸ“Š</div>
385
+ <div class="card-title">Engagement Analytics</div>
386
+ </div>
387
+ <div id="engagementMetrics"></div>
388
+ <div class="chart-container">
389
+ <canvas id="engagementChart"></canvas>
390
+ </div>
391
+ </div>
392
+
393
+ <!-- Property Preferences -->
394
+ <div class="analytics-card">
395
+ <div class="card-header">
396
+ <div class="card-icon" style="background: #764ba2;">🏠</div>
397
+ <div class="card-title">Property Preferences</div>
398
+ </div>
399
+ <div id="propertyPreferences"></div>
400
+ <div class="chart-container">
401
+ <canvas id="propertyChart"></canvas>
402
+ </div>
403
+ </div>
404
+
405
+ <!-- Price Analysis -->
406
+ <div class="analytics-card">
407
+ <div class="card-header">
408
+ <div class="card-icon" style="background: #f39c12;">πŸ’°</div>
409
+ <div class="card-title">Price Analysis</div>
410
+ </div>
411
+ <div id="priceAnalysis"></div>
412
+ </div>
413
+
414
+ <!-- Conversion Probability -->
415
+ <div class="analytics-card">
416
+ <div class="card-header">
417
+ <div class="card-icon" style="background: #e74c3c;">🎯</div>
418
+ <div class="card-title">Conversion Probability</div>
419
+ </div>
420
+ <div id="conversionAnalysis"></div>
421
+ <div class="chart-container">
422
+ <canvas id="conversionChart"></canvas>
423
+ </div>
424
+ </div>
425
+ </div>
426
+
427
+ <!-- Properties Section -->
428
+ <div class="properties-section fade-in">
429
+ <div class="card-header">
430
+ <div class="card-icon" style="background: #2ecc71;">🏘️</div>
431
+ <div class="card-title">Viewed Properties</div>
432
+ </div>
433
+ <div id="propertiesGrid" class="properties-grid"></div>
434
+ </div>
435
+
436
+ <!-- Recommendations Section -->
437
+ <div class="recommendations fade-in">
438
+ <div class="card-header">
439
+ <div class="card-icon" style="background: #9b59b6;">πŸ’‘</div>
440
+ <div class="card-title">AI Recommendations</div>
441
+ </div>
442
+ <div id="recommendationsContent"></div>
443
+ </div>
444
+ </div>
445
+ </div>
446
+
447
+ <script>
448
+ // Global variables
449
+ let currentData = null;
450
+ let charts = {};
451
+
452
+ // Initialize
453
+ document.addEventListener('DOMContentLoaded', function() {
454
+ const customerId = document.getElementById('customerId').value;
455
+ if (customerId) {
456
+ fetchAnalysis(customerId);
457
+ }
458
+ });
459
+
460
+ // Form submission
461
+ document.getElementById('searchForm').addEventListener('submit', function(e) {
462
+ e.preventDefault();
463
+ const customerId = document.getElementById('customerId').value;
464
+ if (customerId) {
465
+ fetchAnalysis(customerId);
466
+ }
467
+ });
468
+
469
+ // Fetch analysis data
470
+ async function fetchAnalysis(customerId) {
471
+ showLoading();
472
+ hideError();
473
+ hideResults();
474
+
475
+ try {
476
+ const response = await fetch(`/api/customer/${customerId}`);
477
+ const data = await response.json();
478
+
479
+ if (data.success) {
480
+ currentData = data;
481
+ displayResults(data);
482
+ showResults();
483
+ } else {
484
+ showError(data.error || 'Failed to fetch analysis');
485
+ }
486
+ } catch (error) {
487
+ showError(`Error: ${error.message}`);
488
+ } finally {
489
+ hideLoading();
490
+ }
491
+ }
492
+
493
+ // Display results
494
+ function displayResults(data) {
495
+ const leadData = data.data.lead_qualification;
496
+ const analytics = data.data.analytics;
497
+ const properties = data.data.properties;
498
+
499
+ // Update lead status
500
+ updateLeadStatus(leadData);
501
+
502
+ // Update analytics cards
503
+ updateEngagementMetrics(analytics);
504
+ updatePropertyPreferences(analytics);
505
+ updatePriceAnalysis(analytics);
506
+ updateConversionAnalysis(analytics);
507
+
508
+ // Update properties
509
+ updatePropertiesGrid(properties);
510
+
511
+ // Update recommendations
512
+ updateRecommendations(analytics.recommendations);
513
+
514
+ // Create charts
515
+ createCharts(analytics);
516
+ }
517
+
518
+ // Update lead status
519
+ function updateLeadStatus(leadData) {
520
+ const statusBadge = document.getElementById('statusBadge');
521
+ const statusDescription = document.getElementById('statusDescription');
522
+ const scoreCircle = document.getElementById('scoreCircle');
523
+
524
+ statusBadge.textContent = leadData.lead_status;
525
+ statusBadge.style.backgroundColor = leadData.status_color;
526
+ statusDescription.textContent = leadData.status_description;
527
+ scoreCircle.textContent = `${Math.round(leadData.lead_score)}/100`;
528
+ }
529
+
530
+ // Update engagement metrics
531
+ function updateEngagementMetrics(analytics) {
532
+ const container = document.getElementById('engagementMetrics');
533
+ container.innerHTML = `
534
+ <div class="metric-item">
535
+ <span class="metric-label">Engagement Level</span>
536
+ <span class="metric-value">${analytics.engagement_level}</span>
537
+ </div>
538
+ <div class="metric-item">
539
+ <span class="metric-label">Total Views</span>
540
+ <span class="metric-value">${currentData.data.summary.total_views}</span>
541
+ </div>
542
+ <div class="metric-item">
543
+ <span class="metric-label">Total Duration</span>
544
+ <span class="metric-value">${formatDuration(currentData.data.summary.total_duration)}</span>
545
+ </div>
546
+ <div class="metric-item">
547
+ <span class="metric-label">Engagement Score</span>
548
+ <span class="metric-value">${Math.round(currentData.data.summary.engagement_score)}</span>
549
+ </div>
550
+ `;
551
+ }
552
+
553
+ // Update property preferences
554
+ function updatePropertyPreferences(analytics) {
555
+ const container = document.getElementById('propertyPreferences');
556
+ const preferredTypes = analytics.preferred_property_types || [];
557
+
558
+ container.innerHTML = preferredTypes.map((type, index) => `
559
+ <div class="metric-item">
560
+ <span class="metric-label">Preference ${index + 1}</span>
561
+ <span class="metric-value">${type}</span>
562
+ </div>
563
+ `).join('');
564
+ }
565
+
566
+ // Update price analysis
567
+ function updatePriceAnalysis(analytics) {
568
+ const container = document.getElementById('priceAnalysis');
569
+ const priceData = analytics.price_preferences || {};
570
+
571
+ container.innerHTML = `
572
+ <div class="metric-item">
573
+ <span class="metric-label">Average Price</span>
574
+ <span class="metric-value">${formatPrice(priceData.avg_price || 0)}</span>
575
+ </div>
576
+ <div class="metric-item">
577
+ <span class="metric-label">Min Price</span>
578
+ <span class="metric-value">${formatPrice(priceData.min_price || 0)}</span>
579
+ </div>
580
+ <div class="metric-item">
581
+ <span class="metric-label">Max Price</span>
582
+ <span class="metric-value">${formatPrice(priceData.max_price || 0)}</span>
583
+ </div>
584
+ <div class="metric-item">
585
+ <span class="metric-label">Price Range</span>
586
+ <span class="metric-value">${formatPrice(priceData.price_range || 0)}</span>
587
+ </div>
588
+ `;
589
+ }
590
+
591
+ // Update conversion analysis
592
+ function updateConversionAnalysis(analytics) {
593
+ const container = document.getElementById('conversionAnalysis');
594
+ const conversionData = analytics.conversion_probability || {};
595
+
596
+ container.innerHTML = `
597
+ <div class="metric-item">
598
+ <span class="metric-label">Conversion Probability</span>
599
+ <span class="metric-value">${Math.round(conversionData.final_probability || 0)}%</span>
600
+ </div>
601
+ <div class="metric-item">
602
+ <span class="metric-label">Confidence Level</span>
603
+ <span class="metric-value">${conversionData.confidence_level || 'Unknown'}</span>
604
+ </div>
605
+ <div class="metric-item">
606
+ <span class="metric-label">Opportunity Score</span>
607
+ <span class="metric-value">${Math.round(analytics.opportunity_score || 0)}</span>
608
+ </div>
609
+ <div class="metric-item">
610
+ <span class="metric-label">Risk Level</span>
611
+ <span class="metric-value">${analytics.risk_assessment?.risk_level || 'Unknown'}</span>
612
+ </div>
613
+ `;
614
+ }
615
+
616
+ // Update properties grid
617
+ function updatePropertiesGrid(properties) {
618
+ const container = document.getElementById('propertiesGrid');
619
+
620
+ container.innerHTML = properties.map(property => `
621
+ <div class="property-card">
622
+ <div class="property-name">${property.propertyName || 'Unknown Property'}</div>
623
+ <div class="property-price">${formatPrice(property.price || 0)}</div>
624
+ <div class="property-stats">
625
+ <span>Views: ${property.viewCount || 0}</span>
626
+ <span>Duration: ${formatDuration(property.totalDuration || 0)}</span>
627
+ <span>Type: ${property.propertyTypeName || 'Unknown'}</span>
628
+ </div>
629
+ </div>
630
+ `).join('');
631
+ }
632
+
633
+ // Update recommendations
634
+ function updateRecommendations(recommendations) {
635
+ const container = document.getElementById('recommendationsContent');
636
+
637
+ if (recommendations && recommendations.length > 0) {
638
+ container.innerHTML = recommendations.map(rec => `
639
+ <div class="recommendation-item">${rec}</div>
640
+ `).join('');
641
+ } else {
642
+ container.innerHTML = '<div class="recommendation-item">No specific recommendations available.</div>';
643
+ }
644
+ }
645
+
646
+ // Create charts
647
+ function createCharts(analytics) {
648
+ createEngagementChart(analytics);
649
+ createPropertyChart(analytics);
650
+ createConversionChart(analytics);
651
+ }
652
+
653
+ // Create engagement chart
654
+ function createEngagementChart(analytics) {
655
+ const ctx = document.getElementById('engagementChart').getContext('2d');
656
+
657
+ if (charts.engagement) {
658
+ charts.engagement.destroy();
659
+ }
660
+
661
+ charts.engagement = new Chart(ctx, {
662
+ type: 'doughnut',
663
+ data: {
664
+ labels: ['High Engagement', 'Medium Engagement', 'Low Engagement'],
665
+ datasets: [{
666
+ data: [40, 35, 25], // Sample data
667
+ backgroundColor: ['#667eea', '#764ba2', '#95a5a6']
668
+ }]
669
+ },
670
+ options: {
671
+ responsive: true,
672
+ maintainAspectRatio: false,
673
+ plugins: {
674
+ legend: {
675
+ position: 'bottom'
676
+ }
677
+ }
678
+ }
679
+ });
680
+ }
681
+
682
+ // Create property chart
683
+ function createPropertyChart(analytics) {
684
+ const ctx = document.getElementById('propertyChart').getContext('2d');
685
+
686
+ if (charts.property) {
687
+ charts.property.destroy();
688
+ }
689
+
690
+ const propertyTypes = analytics.preferred_property_types || [];
691
+ const labels = propertyTypes.slice(0, 5);
692
+ const data = labels.map((_, i) => Math.max(1, 5 - i));
693
+
694
+ charts.property = new Chart(ctx, {
695
+ type: 'bar',
696
+ data: {
697
+ labels: labels,
698
+ datasets: [{
699
+ label: 'Interest Level',
700
+ data: data,
701
+ backgroundColor: '#667eea'
702
+ }]
703
+ },
704
+ options: {
705
+ responsive: true,
706
+ maintainAspectRatio: false,
707
+ plugins: {
708
+ legend: {
709
+ display: false
710
+ }
711
+ },
712
+ scales: {
713
+ y: {
714
+ beginAtZero: true
715
+ }
716
+ }
717
+ }
718
+ });
719
+ }
720
+
721
+ // Create conversion chart
722
+ function createConversionChart(analytics) {
723
+ const ctx = document.getElementById('conversionChart').getContext('2d');
724
+
725
+ if (charts.conversion) {
726
+ charts.conversion.destroy();
727
+ }
728
+
729
+ const conversionProb = analytics.conversion_probability?.final_probability || 0;
730
+
731
+ charts.conversion = new Chart(ctx, {
732
+ type: 'doughnut',
733
+ data: {
734
+ labels: ['Conversion Probability', 'Remaining'],
735
+ datasets: [{
736
+ data: [conversionProb, 100 - conversionProb],
737
+ backgroundColor: ['#e74c3c', '#ecf0f1'],
738
+ borderWidth: 0
739
+ }]
740
+ },
741
+ options: {
742
+ responsive: true,
743
+ maintainAspectRatio: false,
744
+ cutout: '70%',
745
+ plugins: {
746
+ legend: {
747
+ display: false
748
+ }
749
+ }
750
+ }
751
+ });
752
+ }
753
+
754
+ // Utility functions
755
+ function formatPrice(price) {
756
+ return `β‚Ή${price.toLocaleString('en-IN')}`;
757
+ }
758
+
759
+ function formatDuration(seconds) {
760
+ const hours = Math.floor(seconds / 3600);
761
+ const minutes = Math.floor((seconds % 3600) / 60);
762
+ return `${hours}h ${minutes}m`;
763
+ }
764
+
765
+ function showLoading() {
766
+ document.getElementById('loadingSection').style.display = 'block';
767
+ document.getElementById('searchBtn').disabled = true;
768
+ document.getElementById('searchBtn').textContent = 'πŸ”„ Analyzing...';
769
+ }
770
+
771
+ function hideLoading() {
772
+ document.getElementById('loadingSection').style.display = 'none';
773
+ document.getElementById('searchBtn').disabled = false;
774
+ document.getElementById('searchBtn').textContent = 'πŸ” Analyze Customer';
775
+ }
776
+
777
+ function showError(message) {
778
+ const errorSection = document.getElementById('errorSection');
779
+ errorSection.textContent = message;
780
+ errorSection.style.display = 'block';
781
+ }
782
+
783
+ function hideError() {
784
+ document.getElementById('errorSection').style.display = 'none';
785
+ }
786
+
787
+ function showResults() {
788
+ document.getElementById('resultsSection').style.display = 'block';
789
+ }
790
+
791
+ function hideResults() {
792
+ document.getElementById('resultsSection').style.display = 'none';
793
+ }
794
+ </script>
795
+ </body>
796
+ </html>
templates/templates/ai_lead_analysis.html ADDED
@@ -0,0 +1,2152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI-Enhanced Lead Analysis Dashboard</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
9
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
10
+ <style>
11
+ :root {
12
+ --primary-color: #667eea;
13
+ --secondary-color: #764ba2;
14
+ --accent-color: #ff6b6b;
15
+ --success-color: #2ecc71;
16
+ --warning-color: #f39c12;
17
+ --danger-color: #e74c3c;
18
+ --light-color: #f8f9fa;
19
+ --dark-color: #343a40;
20
+ --info-color: #17a2b8;
21
+ }
22
+
23
+ * {
24
+ margin: 0;
25
+ padding: 0;
26
+ box-sizing: border-box;
27
+ }
28
+
29
+ body {
30
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
31
+ background-color: #f5f7fa;
32
+ color: var(--dark-color);
33
+ line-height: 1.6;
34
+ }
35
+
36
+ .gradient-bg {
37
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
38
+ }
39
+
40
+ .container {
41
+ max-width: 1400px;
42
+ margin: 0 auto;
43
+ padding: 20px;
44
+ }
45
+
46
+ .header {
47
+ text-align: center;
48
+ color: white;
49
+ margin-bottom: 30px;
50
+ padding: 20px 0;
51
+ }
52
+
53
+ .header h1 {
54
+ font-size: 2.8rem;
55
+ margin-bottom: 10px;
56
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
57
+ }
58
+
59
+ .header p {
60
+ font-size: 1.2rem;
61
+ opacity: 0.9;
62
+ }
63
+
64
+ .ai-badge {
65
+ background: linear-gradient(45deg, var(--accent-color), #4ecdc4);
66
+ color: white;
67
+ padding: 8px 20px;
68
+ border-radius: 30px;
69
+ font-size: 1rem;
70
+ font-weight: bold;
71
+ display: inline-block;
72
+ margin-top: 10px;
73
+ animation: pulse 2s infinite;
74
+ }
75
+
76
+ @keyframes pulse {
77
+ 0% { transform: scale(1); }
78
+ 50% { transform: scale(1.05); }
79
+ 100% { transform: scale(1); }
80
+ }
81
+
82
+ .card {
83
+ background: white;
84
+ border-radius: 15px;
85
+ padding: 30px;
86
+ margin-bottom: 30px;
87
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
88
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
89
+ }
90
+
91
+ .card:hover {
92
+ transform: translateY(-5px);
93
+ box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
94
+ }
95
+
96
+ .search-form {
97
+ display: flex;
98
+ gap: 15px;
99
+ align-items: end;
100
+ justify-content: center;
101
+ flex-wrap: wrap;
102
+ }
103
+
104
+ .input-group {
105
+ display: flex;
106
+ flex-direction: column;
107
+ min-width: 200px;
108
+ }
109
+
110
+ .input-group label {
111
+ font-weight: 600;
112
+ margin-bottom: 8px;
113
+ color: var(--dark-color);
114
+ }
115
+
116
+ .input-group input, .input-group select {
117
+ padding: 12px 15px;
118
+ border: 2px solid #e0e0e0;
119
+ border-radius: 8px;
120
+ font-size: 16px;
121
+ transition: all 0.3s ease;
122
+ }
123
+
124
+ .input-group input:focus, .input-group select:focus {
125
+ outline: none;
126
+ border-color: var(--primary-color);
127
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
128
+ }
129
+
130
+ .btn {
131
+ padding: 12px 25px;
132
+ border: none;
133
+ border-radius: 8px;
134
+ font-size: 16px;
135
+ font-weight: 600;
136
+ cursor: pointer;
137
+ transition: all 0.3s ease;
138
+ text-decoration: none;
139
+ display: inline-block;
140
+ text-align: center;
141
+ margin: 5px 0;
142
+ }
143
+
144
+ .btn-primary {
145
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
146
+ color: white;
147
+ }
148
+
149
+ .btn-secondary {
150
+ background-color: var(--secondary-color);
151
+ color: white;
152
+ }
153
+
154
+ .btn-accent {
155
+ background: linear-gradient(135deg, var(--accent-color) 0%, #4ecdc4 100%);
156
+ color: white;
157
+ }
158
+
159
+ .btn-success {
160
+ background: linear-gradient(135deg, var(--success-color) 0%, #27ae60 100%);
161
+ color: white;
162
+ }
163
+
164
+ .btn-warning {
165
+ background: linear-gradient(135deg, var(--warning-color) 0%, #d35400 100%);
166
+ color: white;
167
+ }
168
+
169
+ .btn-danger {
170
+ background: linear-gradient(135deg, var(--danger-color) 0%, #c0392b 100%);
171
+ color: white;
172
+ }
173
+
174
+ .btn-info {
175
+ background: linear-gradient(135deg, var(--info-color) 0%, #138d75 100%);
176
+ color: white;
177
+ }
178
+
179
+ .btn:hover {
180
+ transform: translateY(-2px);
181
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
182
+ }
183
+
184
+ .btn:disabled {
185
+ opacity: 0.6;
186
+ cursor: not-allowed;
187
+ transform: none;
188
+ }
189
+
190
+ .action-buttons {
191
+ display: flex;
192
+ gap: 10px;
193
+ flex-wrap: wrap;
194
+ justify-content: center;
195
+ }
196
+
197
+ .loading {
198
+ text-align: center;
199
+ padding: 40px;
200
+ color: var(--dark-color);
201
+ font-size: 18px;
202
+ }
203
+
204
+ .loading-spinner {
205
+ border: 4px solid rgba(0, 0, 0, 0.1);
206
+ border-top: 4px solid var(--primary-color);
207
+ border-radius: 50%;
208
+ width: 50px;
209
+ height: 50px;
210
+ animation: spin 1s linear infinite;
211
+ margin: 20px auto;
212
+ }
213
+
214
+ @keyframes spin {
215
+ 0% { transform: rotate(0deg); }
216
+ 100% { transform: rotate(360deg); }
217
+ }
218
+
219
+ .alert {
220
+ padding: 15px;
221
+ margin-bottom: 20px;
222
+ border-radius: 8px;
223
+ }
224
+
225
+ .alert-success {
226
+ background-color: rgba(46, 204, 113, 0.2);
227
+ border-left: 4px solid var(--success-color);
228
+ color: var(--success-color);
229
+ }
230
+
231
+ .alert-danger {
232
+ background-color: rgba(231, 76, 60, 0.2);
233
+ border-left: 4px solid var(--danger-color);
234
+ color: var(--danger-color);
235
+ }
236
+
237
+ .alert-info {
238
+ background-color: rgba(23, 162, 184, 0.2);
239
+ border-left: 4px solid var(--info-color);
240
+ color: var(--info-color);
241
+ }
242
+
243
+ .alert-warning {
244
+ background-color: rgba(243, 156, 18, 0.2);
245
+ border-left: 4px solid var(--warning-color);
246
+ color: var(--warning-color);
247
+ }
248
+
249
+ .results-section {
250
+ display: none;
251
+ }
252
+
253
+ .section {
254
+ background: white;
255
+ border-radius: 15px;
256
+ padding: 25px;
257
+ margin-bottom: 20px;
258
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
259
+ }
260
+
261
+ .status-header {
262
+ display: flex;
263
+ align-items: center;
264
+ justify-content: space-between;
265
+ margin-bottom: 20px;
266
+ flex-wrap: wrap;
267
+ gap: 15px;
268
+ }
269
+
270
+ .status-badge {
271
+ padding: 8px 18px;
272
+ border-radius: 25px;
273
+ font-weight: bold;
274
+ font-size: 14px;
275
+ text-transform: uppercase;
276
+ letter-spacing: 1px;
277
+ }
278
+
279
+ .lead-score {
280
+ text-align: center;
281
+ }
282
+
283
+ .score-circle {
284
+ width: 100px;
285
+ height: 100px;
286
+ border-radius: 50%;
287
+ display: flex;
288
+ align-items: center;
289
+ justify-content: center;
290
+ margin: 0 auto 10px;
291
+ font-size: 20px;
292
+ font-weight: bold;
293
+ color: white;
294
+ background: conic-gradient(var(--primary-color) 0deg, var(--secondary-color) 360deg);
295
+ }
296
+
297
+ .ai-insights {
298
+ background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
299
+ }
300
+
301
+ .ai-insight-item {
302
+ background: rgba(255, 255, 255, 0.8);
303
+ padding: 15px;
304
+ border-radius: 10px;
305
+ margin-bottom: 10px;
306
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
307
+ }
308
+
309
+ .ai-insight-label {
310
+ font-weight: 600;
311
+ color: var(--dark-color);
312
+ margin-bottom: 5px;
313
+ }
314
+
315
+ .ai-insight-value {
316
+ color: var(--dark-color);
317
+ font-style: italic;
318
+ }
319
+
320
+ .email-form {
321
+ display: flex;
322
+ gap: 15px;
323
+ align-items: end;
324
+ flex-wrap: wrap;
325
+ }
326
+
327
+ .recommendation-card {
328
+ background: #f8f9ff;
329
+ border: 2px solid #e0e6ff;
330
+ border-radius: 10px;
331
+ padding: 20px;
332
+ margin-bottom: 15px;
333
+ transition: all 0.3s ease;
334
+ box-shadow: 0 3px 10px rgba(0, 0, 0, 0.05);
335
+ }
336
+
337
+ .recommendation-card:hover {
338
+ border-color: var(--primary-color);
339
+ transform: translateY(-3px);
340
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
341
+ }
342
+
343
+ .recommendation-name {
344
+ font-weight: 600;
345
+ color: var(--dark-color);
346
+ margin-bottom: 8px;
347
+ font-size: 18px;
348
+ }
349
+
350
+ .recommendation-price {
351
+ font-size: 20px;
352
+ font-weight: bold;
353
+ color: var(--primary-color);
354
+ margin-bottom: 10px;
355
+ }
356
+
357
+ .recommendation-reason {
358
+ background: #fff3cd;
359
+ border-left: 4px solid var(--warning-color);
360
+ padding: 10px;
361
+ border-radius: 5px;
362
+ margin-top: 10px;
363
+ font-style: italic;
364
+ color: #856404;
365
+ font-size: 14px;
366
+ }
367
+
368
+ .analytics-grid {
369
+ display: grid;
370
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
371
+ gap: 20px;
372
+ margin-bottom: 20px;
373
+ }
374
+
375
+ .analytics-card {
376
+ background: white;
377
+ border-radius: 12px;
378
+ padding: 20px;
379
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
380
+ transition: transform 0.3s ease;
381
+ }
382
+
383
+ .analytics-card:hover {
384
+ transform: translateY(-5px);
385
+ }
386
+
387
+ .card-header {
388
+ display: flex;
389
+ align-items: center;
390
+ margin-bottom: 15px;
391
+ }
392
+
393
+ .card-icon {
394
+ width: 40px;
395
+ height: 40px;
396
+ border-radius: 10px;
397
+ display: flex;
398
+ align-items: center;
399
+ justify-content: center;
400
+ margin-right: 12px;
401
+ font-size: 20px;
402
+ color: white;
403
+ }
404
+
405
+ .card-title {
406
+ font-size: 18px;
407
+ font-weight: 600;
408
+ color: var(--dark-color);
409
+ }
410
+
411
+ .metric-item {
412
+ display: flex;
413
+ justify-content: space-between;
414
+ align-items: center;
415
+ padding: 10px 0;
416
+ border-bottom: 1px solid #f0f0f0;
417
+ }
418
+
419
+ .metric-item:last-child {
420
+ border-bottom: none;
421
+ }
422
+
423
+ .metric-label {
424
+ color: var(--dark-color);
425
+ font-weight: 500;
426
+ font-size: 14px;
427
+ }
428
+
429
+ .metric-value {
430
+ font-weight: 600;
431
+ color: var(--dark-color);
432
+ font-size: 14px;
433
+ }
434
+
435
+ .status-indicator {
436
+ display: inline-block;
437
+ padding: 5px 10px;
438
+ border-radius: 15px;
439
+ font-size: 12px;
440
+ font-weight: bold;
441
+ text-transform: uppercase;
442
+ }
443
+
444
+ .status-processing {
445
+ background: #fff3cd;
446
+ color: #856404;
447
+ }
448
+
449
+ .status-completed {
450
+ background: #d4edda;
451
+ color: #155724;
452
+ }
453
+
454
+ .status-failed {
455
+ background: #f8d7da;
456
+ color: #721c24;
457
+ }
458
+
459
+ .form-group {
460
+ margin-bottom: 15px;
461
+ }
462
+
463
+ .form-control {
464
+ width: 100%;
465
+ padding: 10px;
466
+ border: 1px solid #ddd;
467
+ border-radius: 4px;
468
+ font-size: 14px;
469
+ }
470
+
471
+ .row {
472
+ display: flex;
473
+ flex-wrap: wrap;
474
+ margin: 0 -15px;
475
+ }
476
+
477
+ .col-md-6, .col-md-4, .col-md-3, .col-md-8 {
478
+ padding: 0 15px;
479
+ box-sizing: border-box;
480
+ }
481
+
482
+ .col-md-6 {
483
+ flex: 0 0 50%;
484
+ max-width: 50%;
485
+ }
486
+
487
+ .col-md-4 {
488
+ flex: 0 0 33.333333%;
489
+ max-width: 33.333333%;
490
+ }
491
+
492
+ .col-md-3 {
493
+ flex: 0 0 25%;
494
+ max-width: 25%;
495
+ }
496
+
497
+ .col-md-8 {
498
+ flex: 0 0 66.666667%;
499
+ max-width: 66.666667%;
500
+ }
501
+
502
+ @media (max-width: 768px) {
503
+ .search-form, .email-form, .action-buttons {
504
+ flex-direction: column;
505
+ align-items: stretch;
506
+ }
507
+
508
+ .input-group {
509
+ min-width: auto;
510
+ }
511
+
512
+ .status-header {
513
+ flex-direction: column;
514
+ text-align: center;
515
+ }
516
+
517
+ .analytics-grid {
518
+ grid-template-columns: 1fr;
519
+ }
520
+
521
+ .col-md-6, .col-md-4, .col-md-3, .col-md-8 {
522
+ flex: 0 0 100%;
523
+ max-width: 100%;
524
+ }
525
+ }
526
+
527
+ .fade-in {
528
+ animation: fadeIn 0.6s ease-in-out;
529
+ }
530
+
531
+ @keyframes fadeIn {
532
+ from {
533
+ opacity: 0;
534
+ transform: translateY(20px);
535
+ }
536
+ to {
537
+ opacity: 1;
538
+ transform: translateY(0);
539
+ }
540
+ }
541
+
542
+ .small {
543
+ font-size: 0.875rem;
544
+ }
545
+
546
+ .text-muted {
547
+ color: #6c757d !important;
548
+ }
549
+
550
+ .mt-2 {
551
+ margin-top: 0.5rem !important;
552
+ }
553
+
554
+ .mt-3 {
555
+ margin-top: 1rem !important;
556
+ }
557
+
558
+ .mb-0 {
559
+ margin-bottom: 0 !important;
560
+ }
561
+
562
+ .mb-2 {
563
+ margin-bottom: 0.5rem !important;
564
+ }
565
+
566
+ .mb-3 {
567
+ margin-bottom: 1rem !important;
568
+ }
569
+
570
+ .mb-4 {
571
+ margin-bottom: 1.5rem !important;
572
+ }
573
+
574
+ .text-center {
575
+ text-align: center !important;
576
+ }
577
+
578
+ .btn-block {
579
+ display: block;
580
+ width: 100%;
581
+ }
582
+
583
+ .btn-lg {
584
+ padding: 0.5rem 1rem;
585
+ font-size: 1.25rem;
586
+ line-height: 1.5;
587
+ border-radius: 0.3rem;
588
+ }
589
+
590
+ .badge {
591
+ display: inline-block;
592
+ padding: 0.25em 0.4em;
593
+ font-size: 75%;
594
+ font-weight: 700;
595
+ line-height: 1;
596
+ text-align: center;
597
+ white-space: nowrap;
598
+ vertical-align: baseline;
599
+ border-radius: 0.25rem;
600
+ }
601
+
602
+ .bg-success {
603
+ background-color: var(--success-color) !important;
604
+ }
605
+
606
+ .bg-info {
607
+ background-color: var(--info-color) !important;
608
+ }
609
+
610
+ .bg-warning {
611
+ background-color: var(--warning-color) !important;
612
+ }
613
+
614
+ .bg-light {
615
+ background-color: #f8f9fa !important;
616
+ }
617
+
618
+ .text-dark {
619
+ color: var(--dark-color) !important;
620
+ }
621
+
622
+ .float-end {
623
+ float: right !important;
624
+ }
625
+
626
+ .mb-3 {
627
+ margin-bottom: 1rem !important;
628
+ }
629
+
630
+ .text-uppercase {
631
+ text-transform: uppercase !important;
632
+ }
633
+
634
+ .shadow-sm {
635
+ box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;
636
+ }
637
+
638
+ .rounded {
639
+ border-radius: 0.25rem !important;
640
+ }
641
+
642
+ .p-3 {
643
+ padding: 1rem !important;
644
+ }
645
+
646
+ .h-100 {
647
+ height: 100% !important;
648
+ }
649
+ </style>
650
+ </head>
651
+ <body>
652
+ <div class="gradient-bg">
653
+ <div class="container header">
654
+ <h1>πŸ€– AI-Enhanced Lead Analysis</h1>
655
+ <p>Advanced Customer Behavior Analytics with AI-Powered Recommendations</p>
656
+ <div class="ai-badge">🧠 Powered by Hugging Face AI Models</div>
657
+ </div>
658
+ </div>
659
+
660
+ <div class="container">
661
+ <div class="card search-section">
662
+ <form class="search-form" id="searchForm">
663
+ <div class="input-group">
664
+ <label for="customerId">Customer ID</label>
665
+ <input type="number" id="customerId" name="customerId" placeholder="Enter Customer ID" required value="{{ customer_id if customer_id else '' }}">
666
+ </div>
667
+ <div class="action-buttons">
668
+ <button type="submit" class="btn btn-primary" id="searchBtn">
669
+ <i class="fas fa-search"></i> Analyze Customer
670
+ </button>
671
+ <button type="button" class="btn btn-accent" id="aiAnalyzeBtn" disabled>
672
+ <i class="fas fa-robot"></i> AI Analysis
673
+ </button>
674
+ </div>
675
+ </form>
676
+ </div>
677
+
678
+ <div class="card email-section" id="emailSection" style="display: none;">
679
+ <h3><i class="fas fa-envelope"></i> Email Automation & Testing</h3>
680
+ <p>Test email functionality and send automated AI-powered recommendations.</p>
681
+
682
+ <!-- Email Testing Section -->
683
+ <div class="section">
684
+ <h4><i class="fas fa-vial"></i> Email System Testing</h4>
685
+ <p>Test the email automation system functionality.</p>
686
+ <div class="row">
687
+ <div class="col-md-6">
688
+ <div class="form-group">
689
+ <label for="testEmail">Test Email Address:</label>
690
+ <input type="email" id="testEmail" class="form-control" value="shaiksameermujahid@gmail.com" placeholder="Enter email address">
691
+ </div>
692
+ </div>
693
+ <div class="col-md-6">
694
+ <div class="form-group">
695
+ <label>&nbsp;</label>
696
+ <div>
697
+ <button type="button" class="btn btn-primary" onclick="testEmail()">
698
+ <i class="fas fa-vial"></i> Test SendGrid Email
699
+ </button>
700
+ <button type="button" class="btn btn-info" onclick="testEmailSimple()">
701
+ <i class="fas fa-vial"></i> Simple Email Test (GET)
702
+ </button>
703
+ </div>
704
+ </div>
705
+ </div>
706
+ </div>
707
+ <div id="emailTestResult" class="mt-3"></div>
708
+ </div>
709
+
710
+ <!-- Automated Email Analysis Section -->
711
+ <div class="section gradient-bg">
712
+ <h4><i class="fas fa-robot"></i> AI-Powered Email Analysis & Testing</h4>
713
+ <p><strong>How it works:</strong> Enter a Customer ID, and our AI will analyze their behavior patterns to determine exactly which automated emails would be triggered and why. Then test sending all triggered emails!</p>
714
+
715
+ <!-- Customer ID Input -->
716
+ <div class="row mb-4">
717
+ <div class="col-md-6">
718
+ <div class="form-group">
719
+ <label><i class="fas fa-user"></i> Customer ID:</label>
720
+ <input type="number" id="customerId" class="form-control" value="105" placeholder="Enter Customer ID (e.g., 105)">
721
+ </div>
722
+ </div>
723
+ <div class="col-md-6">
724
+ <div class="form-group">
725
+ <label><i class="fas fa-envelope"></i> Test Email:</label>
726
+ <input type="email" id="automatedEmailTest" class="form-control" value="shaiksameermujahid@gmail.com" placeholder="Email for testing">
727
+ </div>
728
+ </div>
729
+ </div>
730
+
731
+ <!-- Main Action Buttons -->
732
+ <div class="row mb-4">
733
+ <div class="col-md-6">
734
+ <div class="text-center">
735
+ <h6><i class="fas fa-chart-line"></i> Step 1: Analyze Email Triggers</h6>
736
+ <p>See exactly which automated emails would be sent based on customer behavior patterns</p>
737
+ <button type="button" class="btn btn-light btn-lg" onclick="analyzeEmailTriggers()">
738
+ <i class="fas fa-chart-line"></i> Analyze Email Triggers
739
+ </button>
740
+ </div>
741
+ </div>
742
+ <div class="col-md-6">
743
+ <div class="text-center">
744
+ <h6><i class="fas fa-paper-plane"></i> Step 2: Test Automated Emails</h6>
745
+ <p>Send all triggered emails to test the automated system</p>
746
+ <button type="button" class="btn btn-success btn-lg" onclick="testAutomatedEmails()">
747
+ <i class="fas fa-paper-plane"></i> Test All Emails
748
+ </button>
749
+ </div>
750
+ </div>
751
+ </div>
752
+
753
+ <!-- AI Analysis Summary -->
754
+ <div id="aiAnalysisSummary" style="display: none;" class="mb-4">
755
+ <div class="alert alert-light">
756
+ <h6><i class="fas fa-brain"></i> AI Analysis Summary</h6>
757
+ <div id="aiAnalysisContent"></div>
758
+ </div>
759
+ </div>
760
+
761
+ <!-- Email Triggers Display -->
762
+ <div id="emailTriggersDisplay" style="display: none;">
763
+ <h6><i class="fas fa-envelope"></i> Email Triggers Based on AI Analysis:</h6>
764
+ <div id="emailTriggersContent"></div>
765
+ </div>
766
+
767
+ <!-- Test Results -->
768
+ <div id="automatedEmailResult" class="mt-3"></div>
769
+ </div>
770
+
771
+ <!-- Property Database Management Section -->
772
+ <div class="section bg-light">
773
+ <h4><i class="fas fa-database"></i> Property Database Management (500+ Properties)</h4>
774
+ <p>Fetch all 500+ properties from <code>/api/Property/allPropertieswithfulldetails</code> and store in ChromaDB for AI analysis and email recommendations.</p>
775
+ <p class="text-muted small">
776
+ <strong>API Parameters:</strong> pageNumber (1-10), pageSize (500), using parallel processing for faster fetching
777
+ </p>
778
+
779
+ <!-- Current Status -->
780
+ <div class="row mb-3">
781
+ <div class="col-md-12">
782
+ <div class="alert alert-info">
783
+ <h6><i class="fas fa-info-circle"></i> Current Database Status:</h6>
784
+ <div id="databaseStatus">
785
+ <p>Loading database status...</p>
786
+ </div>
787
+ <button type="button" class="btn btn-sm btn-outline-info" onclick="checkDatabaseStatus()">
788
+ <i class="fas fa-sync"></i> Refresh Status
789
+ </button>
790
+ </div>
791
+ </div>
792
+ </div>
793
+
794
+ <div class="row">
795
+ <div class="col-md-8">
796
+ <div class="form-group">
797
+ <label><i class="fas fa-download"></i> Fetch All 500+ Properties:</label>
798
+ <p class="text-muted small">Use optimized parallel processing to fetch all available properties</p>
799
+ <div class="row">
800
+ <div class="col-md-3">
801
+ <label class="small">Workers:</label>
802
+ <input type="number" id="workerCount" class="form-control" value="10" min="1" max="20" placeholder="10">
803
+ </div>
804
+ <div class="col-md-3">
805
+ <label class="small">Page Size:</label>
806
+ <input type="number" id="pageSize" class="form-control" value="500" min="100" max="1000" placeholder="500">
807
+ </div>
808
+ <div class="col-md-3">
809
+ <label class="small">Max Pages:</label>
810
+ <input type="number" id="maxPages" class="form-control" value="10" min="1" max="50" placeholder="10">
811
+ </div>
812
+ <div class="col-md-3">
813
+ <label class="small">&nbsp;</label>
814
+ <button type="button" class="btn btn-success btn-block" onclick="fetchAllProperties()">
815
+ <i class="fas fa-download"></i> Fetch All Properties
816
+ </button>
817
+ </div>
818
+ </div>
819
+ </div>
820
+ </div>
821
+ <div class="col-md-4">
822
+ <div class="form-group">
823
+ <label><i class="fas fa-bolt"></i> Quick Test:</label>
824
+ <p class="text-muted small">Test with default settings</p>
825
+ <button type="button" class="btn btn-warning btn-block" onclick="testParallelFetching()">
826
+ <i class="fas fa-bolt"></i> Quick Test
827
+ </button>
828
+ </div>
829
+ </div>
830
+ </div>
831
+
832
+ <div id="fetchResult" class="mt-3"></div>
833
+ </div>
834
+
835
+ <!-- Performance Testing Section -->
836
+ <div class="section bg-warning bg-opacity-10">
837
+ <h4><i class="fas fa-tachometer-alt"></i> Performance Testing</h4>
838
+ <p>Test parallel property fetching and system performance.</p>
839
+ <div class="row">
840
+ <div class="col-md-4">
841
+ <div class="form-group">
842
+ <label for="workerCount">Workers:</label>
843
+ <input type="number" id="workerCount" class="form-control" value="8" min="1" max="20">
844
+ </div>
845
+ </div>
846
+ <div class="col-md-4">
847
+ <div class="form-group">
848
+ <label for="pageSize">Page Size:</label>
849
+ <input type="number" id="pageSize" class="form-control" value="100" min="10" max="500">
850
+ </div>
851
+ </div>
852
+ <div class="col-md-4">
853
+ <div class="form-group">
854
+ <label for="maxPages">Max Pages:</label>
855
+ <input type="number" id="maxPages" class="form-control" value="20" min="1" max="100">
856
+ </div>
857
+ </div>
858
+ </div>
859
+ <div class="row mt-3">
860
+ <div class="col-12">
861
+ <button type="button" class="btn btn-warning" onclick="testParallelFetching()">
862
+ <i class="fas fa-rocket"></i> Test Parallel Property Fetching
863
+ </button>
864
+ <button type="button" class="btn btn-info" onclick="testPerformance()">
865
+ <i class="fas fa-bolt"></i> Test Performance
866
+ </button>
867
+ </div>
868
+ </div>
869
+ <div id="parallelFetchResult" class="mt-3"></div>
870
+ </div>
871
+
872
+ <!-- Manual Email Section -->
873
+ <div class="section">
874
+ <h4><i class="fas fa-envelope"></i> Manual Email Sending</h4>
875
+ <p>Send personalized property recommendations to your email.</p>
876
+ <div class="email-form" id="emailForm">
877
+ <div class="input-group">
878
+ <label for="userEmail">Email Address</label>
879
+ <input type="email" id="userEmail" placeholder="Enter email address" value="sameermujahid7777@gmail.com" required>
880
+ </div>
881
+ <div class="action-buttons">
882
+ <button type="button" class="btn btn-success" id="sendEmailBtn">
883
+ <i class="fas fa-envelope"></i> Send AI Recommendations
884
+ </button>
885
+ <button type="button" class="btn btn-info" id="previewEmailBtn">
886
+ <i class="fas fa-eye"></i> Preview Email
887
+ </button>
888
+ </div>
889
+ </div>
890
+ <div id="emailStatus" class="mt-3"></div>
891
+ </div>
892
+
893
+ <!-- Automated Email Section -->
894
+ <div class="section bg-success bg-opacity-10">
895
+ <h4><i class="fas fa-robot"></i> Automated Email Features</h4>
896
+ <p>Configure automated email sending based on behavioral triggers.</p>
897
+ <div class="action-buttons">
898
+ <button type="button" class="btn btn-success" id="enableAutoEmailBtn">
899
+ <i class="fas fa-check"></i> Enable Automated Emails
900
+ </button>
901
+ <button type="button" class="btn btn-primary" id="configureTriggersBtn">
902
+ <i class="fas fa-cog"></i> Configure Triggers
903
+ </button>
904
+ </div>
905
+ <div id="autoEmailStatus" class="mt-3"></div>
906
+ </div>
907
+ </div>
908
+
909
+ <div id="loadingSection" class="loading text-center" style="display: none;">
910
+ <div class="loading-spinner"></div>
911
+ <div id="loadingText" class="mt-3">πŸ”„ Analyzing customer data...</div>
912
+ <div style="margin-top: 10px; font-size: 14px; opacity: 0.8;" id="loadingSubtext">
913
+ Fetching lead qualification and property analytics
914
+ </div>
915
+ </div>
916
+
917
+ <div id="errorSection" class="alert alert-danger" style="display: none;"></div>
918
+ <div id="successSection" class="alert alert-success" style="display: none;"></div>
919
+
920
+ <div id="resultsSection" class="results-section">
921
+ <!-- Lead Status Section -->
922
+ <div class="section fade-in">
923
+ <div class="status-header">
924
+ <div>
925
+ <h2><i class="fas fa-user-check"></i> Lead Qualification Status</h2>
926
+ <div id="statusBadge" class="status-badge"></div>
927
+ <p id="statusDescription" class="mt-2 text-muted"></p>
928
+ </div>
929
+ <div class="lead-score">
930
+ <div id="scoreCircle" class="score-circle"></div>
931
+ <div>Lead Score</div>
932
+ </div>
933
+ </div>
934
+ </div>
935
+
936
+ <!-- AI Insights Section -->
937
+ <div class="section ai-insights fade-in" id="aiInsightsSection" style="display: none;">
938
+ <h3><i class="fas fa-brain"></i> AI Behavioral Analysis</h3>
939
+ <div id="aiInsightsContent"></div>
940
+ </div>
941
+
942
+ <!-- AI Recommendations Section -->
943
+ <div class="section fade-in" id="aiRecommendationsSection" style="display: none;">
944
+ <h3><i class="fas fa-bullseye"></i> AI-Curated Property Recommendations</h3>
945
+ <div id="aiRecommendationsContent"></div>
946
+ </div>
947
+
948
+ <!-- Analytics Grid -->
949
+ <div class="analytics-grid fade-in">
950
+ <!-- Engagement Analytics -->
951
+ <div class="analytics-card">
952
+ <div class="card-header">
953
+ <div class="card-icon gradient-bg">πŸ“Š</div>
954
+ <div class="card-title">Engagement Analytics</div>
955
+ </div>
956
+ <div id="engagementMetrics"></div>
957
+ </div>
958
+
959
+ <!-- Property Preferences -->
960
+ <div class="analytics-card">
961
+ <div class="card-header">
962
+ <div class="card-icon" style="background: var(--secondary-color);">🏠</div>
963
+ <div class="card-title">Property Preferences</div>
964
+ </div>
965
+ <div id="propertyPreferences"></div>
966
+ </div>
967
+
968
+ <!-- Price Analysis -->
969
+ <div class="analytics-card">
970
+ <div class="card-header">
971
+ <div class="card-icon" style="background: var(--warning-color);">πŸ’°</div>
972
+ <div class="card-title">Price Analysis</div>
973
+ </div>
974
+ <div id="priceAnalysis"></div>
975
+ </div>
976
+
977
+ <!-- Conversion Probability -->
978
+ <div class="analytics-card">
979
+ <div class="card-header">
980
+ <div class="card-icon" style="background: var(--danger-color);">🎯</div>
981
+ <div class="card-title">Conversion Probability</div>
982
+ </div>
983
+ <div id="conversionAnalysis"></div>
984
+ </div>
985
+ </div>
986
+
987
+ <div class="card">
988
+ <div class="card-header">
989
+ <h5><i class="fas fa-rocket"></i> Performance Testing</h5>
990
+ </div>
991
+ <div class="card-body">
992
+ <div class="row">
993
+ <div class="col-md-4">
994
+ <div class="form-group">
995
+ <label for="workerCount">Workers:</label>
996
+ <input type="number" id="workerCount" class="form-control" value="8" min="1" max="20">
997
+ </div>
998
+ </div>
999
+ <div class="col-md-4">
1000
+ <div class="form-group">
1001
+ <label for="pageSize">Page Size:</label>
1002
+ <input type="number" id="pageSize" class="form-control" value="100" min="10" max="500">
1003
+ </div>
1004
+ </div>
1005
+ <div class="col-md-4">
1006
+ <div class="form-group">
1007
+ <label for="maxPages">Max Pages:</label>
1008
+ <input type="number" id="maxPages" class="form-control" value="20" min="1" max="100">
1009
+ </div>
1010
+ </div>
1011
+ </div>
1012
+ <div class="row mt-3">
1013
+ <div class="col-12">
1014
+ <button type="button" class="btn btn-warning" onclick="testParallelFetching()">
1015
+ <i class="fas fa-rocket"></i> Test Parallel Property Fetching
1016
+ </button>
1017
+ <button type="button" class="btn btn-info" onclick="testPerformance()">
1018
+ <i class="fas fa-bolt"></i> Test Performance
1019
+ </button>
1020
+ </div>
1021
+ </div>
1022
+ <div id="parallelFetchResult" class="mt-3"></div>
1023
+ </div>
1024
+ </div>
1025
+ </div>
1026
+ </div>
1027
+
1028
+ <script>
1029
+ // Global variables
1030
+ let currentData = null;
1031
+ let currentCustomerId = null;
1032
+ let aiData = null;
1033
+
1034
+ // Initialize
1035
+ document.addEventListener('DOMContentLoaded', function() {
1036
+ console.log('πŸ€– AI Lead Analysis System initialized');
1037
+
1038
+ // Check database status on page load
1039
+ checkDatabaseStatus();
1040
+
1041
+ // Initialize search form
1042
+ const searchForm = document.getElementById('searchForm');
1043
+ if (searchForm) {
1044
+ searchForm.addEventListener('submit', handleSearch);
1045
+ }
1046
+
1047
+ // Initialize email form
1048
+ const emailForm = document.getElementById('emailForm');
1049
+ if (emailForm) {
1050
+ const sendEmailBtn = document.getElementById('sendEmailBtn');
1051
+ const previewEmailBtn = document.getElementById('previewEmailBtn');
1052
+
1053
+ if (sendEmailBtn) {
1054
+ sendEmailBtn.addEventListener('click', sendEmail);
1055
+ }
1056
+ if (previewEmailBtn) {
1057
+ previewEmailBtn.addEventListener('click', previewEmail);
1058
+ }
1059
+ }
1060
+
1061
+ // Initialize AI analysis button
1062
+ const aiAnalyzeBtn = document.getElementById('aiAnalyzeBtn');
1063
+ if (aiAnalyzeBtn) {
1064
+ aiAnalyzeBtn.addEventListener('click', performAIAnalysis);
1065
+ }
1066
+
1067
+ // Initialize automated email buttons
1068
+ const enableAutoEmailBtn = document.getElementById('enableAutoEmailBtn');
1069
+ const configureTriggersBtn = document.getElementById('configureTriggersBtn');
1070
+
1071
+ if (enableAutoEmailBtn) {
1072
+ enableAutoEmailBtn.addEventListener('click', function() {
1073
+ showSuccess('βœ… Automated emails enabled! The system will now send emails based on customer behavior triggers.');
1074
+ });
1075
+ }
1076
+
1077
+ if (configureTriggersBtn) {
1078
+ configureTriggersBtn.addEventListener('click', function() {
1079
+ showInfo('βš™οΈ Trigger configuration: Currently using AI-powered automatic trigger detection based on customer behavior analysis.');
1080
+ });
1081
+ }
1082
+ });
1083
+
1084
+ // Form submission
1085
+ document.getElementById('searchForm').addEventListener('submit', function(e) {
1086
+ e.preventDefault();
1087
+ const customerId = document.getElementById('customerId').value;
1088
+ if (customerId) {
1089
+ currentCustomerId = customerId;
1090
+ fetchAnalysis(customerId);
1091
+ }
1092
+ });
1093
+
1094
+ // AI Analysis button
1095
+ document.getElementById('aiAnalyzeBtn').addEventListener('click', function() {
1096
+ if (currentCustomerId) {
1097
+ fetchAIAnalysis(currentCustomerId);
1098
+ }
1099
+ });
1100
+
1101
+ // Send email button
1102
+ document.getElementById('sendEmailBtn').addEventListener('click', function() {
1103
+ const email = document.getElementById('userEmail').value;
1104
+ if (email && currentCustomerId) {
1105
+ sendAIRecommendations(currentCustomerId, email);
1106
+ }
1107
+ });
1108
+
1109
+ // Preview email button
1110
+ document.getElementById('previewEmailBtn').addEventListener('click', function() {
1111
+ if (currentCustomerId) {
1112
+ previewEmail(currentCustomerId);
1113
+ } else {
1114
+ showError('Please analyze a customer first');
1115
+ }
1116
+ });
1117
+
1118
+ // Test email system button
1119
+ document.getElementById('testEmailBtn').addEventListener('click', function() {
1120
+ testEmailSystem();
1121
+ });
1122
+
1123
+ // Test automated email button
1124
+ document.getElementById('testAutomatedEmailBtn').addEventListener('click', function() {
1125
+ if (currentCustomerId) {
1126
+ testAutomatedEmail(currentCustomerId);
1127
+ } else {
1128
+ showError('Please analyze a customer first');
1129
+ }
1130
+ });
1131
+
1132
+ // Enable automated emails button
1133
+ document.getElementById('enableAutoEmailBtn').addEventListener('click', function() {
1134
+ enableAutomatedEmails();
1135
+ });
1136
+
1137
+ // Configure triggers button
1138
+ document.getElementById('configureTriggersBtn').addEventListener('click', function() {
1139
+ configureEmailTriggers();
1140
+ });
1141
+
1142
+ // Fetch regular analysis
1143
+ async function fetchAnalysis(customerId) {
1144
+ showLoading('πŸ”„ Analyzing customer data...', 'Fetching lead qualification and property analytics');
1145
+ hideError();
1146
+ hideSuccess();
1147
+ hideResults();
1148
+ try {
1149
+ const response = await fetch(`/api/lead-analysis/${customerId}`);
1150
+ const data = await response.json();
1151
+ if (data.success) {
1152
+ currentData = data;
1153
+ displayResults(data);
1154
+ showResults();
1155
+ showEmailSection();
1156
+ enableAIButton();
1157
+ } else {
1158
+ showError(data.error || 'Failed to fetch analysis');
1159
+ }
1160
+ } catch (error) {
1161
+ showError(`Error: ${error.message}`);
1162
+ } finally {
1163
+ hideLoading();
1164
+ }
1165
+ }
1166
+
1167
+ // Fetch AI analysis
1168
+ async function fetchAIAnalysis(customerId) {
1169
+ showLoading('πŸ€– AI Analysis in progress...', 'Using Hugging Face models for behavioral analysis');
1170
+ hideError();
1171
+ try {
1172
+ const response = await fetch(`/api/ai-analysis/${customerId}`);
1173
+ const data = await response.json();
1174
+ if (data.success) {
1175
+ aiData = data;
1176
+ displayAIResults(data);
1177
+ showSuccess('πŸ€– AI Analysis completed successfully!');
1178
+ } else {
1179
+ showError(data.error || 'Failed to fetch AI analysis');
1180
+ }
1181
+ } catch (error) {
1182
+ showError(`AI Error: ${error.message}`);
1183
+ } finally {
1184
+ hideLoading();
1185
+ }
1186
+ }
1187
+
1188
+ // Send AI recommendations
1189
+ async function sendAIRecommendations(customerId, email) {
1190
+ const sendBtn = document.getElementById('sendEmailBtn');
1191
+ const statusDiv = document.getElementById('emailStatus');
1192
+
1193
+ sendBtn.disabled = true;
1194
+ sendBtn.textContent = 'πŸ“§ Sending...';
1195
+
1196
+ statusDiv.innerHTML = '<div class="status-indicator status-processing">πŸ”„ Processing AI recommendations...</div>';
1197
+ try {
1198
+ const response = await fetch(`/api/ai-recommendations/${customerId}`, {
1199
+ method: 'POST',
1200
+ headers: {
1201
+ 'Content-Type': 'application/json'
1202
+ },
1203
+ body: JSON.stringify({ email: email })
1204
+ });
1205
+ const data = await response.json();
1206
+ if (data.success) {
1207
+ statusDiv.innerHTML = '<div class="status-indicator status-processing">βœ… AI recommendations initiated! Check your email in 2-3 minutes.</div>';
1208
+
1209
+ // Poll for status updates
1210
+ pollAIStatus(customerId, statusDiv);
1211
+ } else {
1212
+ statusDiv.innerHTML = `<div class="status-indicator status-failed">❌ ${data.error}</div>`;
1213
+ }
1214
+ } catch (error) {
1215
+ statusDiv.innerHTML = `<div class="status-indicator status-failed">❌ Error: ${error.message}</div>`;
1216
+ } finally {
1217
+ sendBtn.disabled = false;
1218
+ sendBtn.textContent = 'πŸ“§ Send AI Recommendations';
1219
+ }
1220
+ }
1221
+
1222
+ // Preview email
1223
+ async function previewEmail(customerId) {
1224
+ const previewBtn = document.getElementById('previewEmailBtn');
1225
+ const statusDiv = document.getElementById('emailStatus');
1226
+
1227
+ try {
1228
+ previewBtn.disabled = true;
1229
+ previewBtn.textContent = 'πŸ”„ Generating...';
1230
+ statusDiv.innerHTML = '<div class="status-indicator status-processing">πŸ”„ Generating email preview...</div>';
1231
+
1232
+ // Open email preview in new window
1233
+ const previewUrl = `/email-preview/${customerId}`;
1234
+ window.open(previewUrl, '_blank', 'width=1000,height=800,scrollbars=yes,resizable=yes');
1235
+
1236
+ statusDiv.innerHTML = '<div class="status-indicator status-completed">βœ… Email preview opened in new window!</div>';
1237
+
1238
+ } catch (error) {
1239
+ statusDiv.innerHTML = '<div class="status-indicator status-error">❌ Error: ' + error.message + '</div>';
1240
+ } finally {
1241
+ previewBtn.disabled = false;
1242
+ previewBtn.textContent = 'πŸ‘οΈ Preview Email';
1243
+ }
1244
+ }
1245
+
1246
+ // Poll AI status
1247
+ async function pollAIStatus(customerId, statusDiv) {
1248
+ const maxPolls = 20; // 10 minutes max
1249
+ let polls = 0;
1250
+ const pollInterval = setInterval(async () => {
1251
+ polls++;
1252
+
1253
+ try {
1254
+ const response = await fetch(`/api/ai-status/${customerId}`);
1255
+ const data = await response.json();
1256
+
1257
+ const aiStatus = data.ai_processing || {};
1258
+
1259
+ if (aiStatus.status === 'completed') {
1260
+ clearInterval(pollInterval);
1261
+ statusDiv.innerHTML = '<div class="status-indicator status-completed">πŸŽ‰ AI recommendations sent successfully!</div>';
1262
+ } else if (aiStatus.status === 'failed') {
1263
+ clearInterval(pollInterval);
1264
+ statusDiv.innerHTML = `<div class="status-indicator status-failed">❌ AI processing failed: ${aiStatus.error || 'Unknown error'}</div>`;
1265
+ } else if (polls >= maxPolls) {
1266
+ clearInterval(pollInterval);
1267
+ statusDiv.innerHTML = '<div class="status-indicator status-failed">⏰ Processing timeout. Please try again.</div>';
1268
+ }
1269
+ } catch (error) {
1270
+ // Continue polling on error
1271
+ console.warn('Status polling error:', error);
1272
+ }
1273
+ }, 30000); // Poll every 30 seconds
1274
+ }
1275
+
1276
+ // Display results
1277
+ function displayResults(data) {
1278
+ const leadData = data.data.lead_qualification;
1279
+ const analytics = data.data.analytics;
1280
+ // Update lead status
1281
+ updateLeadStatus(leadData);
1282
+ // Update analytics cards
1283
+ updateEngagementMetrics(analytics);
1284
+ updatePropertyPreferences(analytics);
1285
+ updatePriceAnalysis(analytics);
1286
+ updateConversionAnalysis(analytics);
1287
+ }
1288
+
1289
+ // Display AI results
1290
+ function displayAIResults(data) {
1291
+ const aiInsights = data.data.ai_insights;
1292
+ const aiRecommendations = data.data.ai_recommendations;
1293
+ // Show AI insights
1294
+ updateAIInsights(aiInsights);
1295
+
1296
+ // Show AI recommendations
1297
+ updateAIRecommendations(aiRecommendations);
1298
+ }
1299
+
1300
+ // Update AI insights
1301
+ function updateAIInsights(aiInsights) {
1302
+ const container = document.getElementById('aiInsightsContent');
1303
+ const section = document.getElementById('aiInsightsSection');
1304
+
1305
+ if (!aiInsights || aiInsights.error) {
1306
+ container.innerHTML = '<div class="ai-insight-item">AI analysis not available</div>';
1307
+ section.style.display = 'block';
1308
+ return;
1309
+ }
1310
+ const insights = [
1311
+ { label: 'AI Personality Type', value: aiInsights.ai_personality_type || 'Unknown' },
1312
+ { label: 'Buying Motivation', value: aiInsights.buying_motivation || 'Unknown' },
1313
+ { label: 'Recommendation Strategy', value: aiInsights.recommendation_strategy || 'Unknown' },
1314
+ { label: 'Urgency Level', value: aiInsights.urgency_level || 'Medium' },
1315
+ { label: 'Sentiment Analysis', value: aiInsights.sentiment_analysis ?
1316
+ `${aiInsights.sentiment_analysis[0]?.label} (${Math.round(aiInsights.sentiment_analysis[0]?.score * 100)}%)` : 'Not available' }
1317
+ ];
1318
+ container.innerHTML = insights.map(insight => `
1319
+ <div class="ai-insight-item">
1320
+ <div class="ai-insight-label">${insight.label}:</div>
1321
+ <div class="ai-insight-value">${insight.value}</div>
1322
+ </div>
1323
+ `).join('');
1324
+ section.style.display = 'block';
1325
+ }
1326
+
1327
+ // Update AI recommendations
1328
+ function updateAIRecommendations(recommendations) {
1329
+ const container = document.getElementById('aiRecommendationsContent');
1330
+ const section = document.getElementById('aiRecommendationsSection');
1331
+
1332
+ if (!recommendations || recommendations.length === 0) {
1333
+ container.innerHTML = '<div class="recommendation-card">No AI recommendations available</div>';
1334
+ section.style.display = 'block';
1335
+ return;
1336
+ }
1337
+ container.innerHTML = recommendations.slice(0, 5).map(rec => `
1338
+ <div class="recommendation-card">
1339
+ <div class="recommendation-name">${rec.propertyName || 'Premium Property'}</div>
1340
+ <div class="recommendation-price">${formatPrice(rec.price || 0)}</div>
1341
+ <div><strong>Type:</strong> ${rec.propertyTypeName || 'Residential'}</div>
1342
+ <div><strong>AI Score:</strong> ${Math.round((rec.ai_similarity_score || 0) * 100)}%</div>
1343
+ <div class="recommendation-reason">
1344
+ <strong>Why AI recommends this:</strong> ${rec.recommendation_reason || 'Based on your viewing patterns'}
1345
+ </div>
1346
+ </div>
1347
+ `).join('');
1348
+ section.style.display = 'block';
1349
+ }
1350
+
1351
+ // Update lead status
1352
+ function updateLeadStatus(leadData) {
1353
+ const statusBadge = document.getElementById('statusBadge');
1354
+ const statusDescription = document.getElementById('statusDescription');
1355
+ const scoreCircle = document.getElementById('scoreCircle');
1356
+ statusBadge.textContent = leadData.lead_status;
1357
+ statusBadge.style.backgroundColor = leadData.status_color;
1358
+ statusDescription.textContent = leadData.status_description;
1359
+ scoreCircle.textContent = `${Math.round(leadData.lead_score)}/100`;
1360
+ }
1361
+
1362
+ // Update engagement metrics
1363
+ function updateEngagementMetrics(analytics) {
1364
+ const container = document.getElementById('engagementMetrics');
1365
+ container.innerHTML = `
1366
+ <div class="metric-item">
1367
+ <span class="metric-label">Engagement Level</span>
1368
+ <span class="metric-value">${analytics.engagement_level}</span>
1369
+ </div>
1370
+ <div class="metric-item">
1371
+ <span class="metric-label">Total Views</span>
1372
+ <span class="metric-value">${currentData.data.summary.total_views}</span>
1373
+ </div>
1374
+ <div class="metric-item">
1375
+ <span class="metric-label">Total Duration</span>
1376
+ <span class="metric-value">${formatDuration(currentData.data.summary.total_duration)}</span>
1377
+ </div>
1378
+ <div class="metric-item">
1379
+ <span class="metric-label">Engagement Score</span>
1380
+ <span class="metric-value">${Math.round(currentData.data.summary.engagement_score)}</span>
1381
+ </div>
1382
+ `;
1383
+ }
1384
+
1385
+ // Update property preferences
1386
+ function updatePropertyPreferences(analytics) {
1387
+ const container = document.getElementById('propertyPreferences');
1388
+ const preferredTypes = analytics.preferred_property_types || [];
1389
+
1390
+ container.innerHTML = preferredTypes.map((type, index) => `
1391
+ <div class="metric-item">
1392
+ <span class="metric-label">Preference ${index + 1}</span>
1393
+ <span class="metric-value">${type}</span>
1394
+ </div>
1395
+ `).join('');
1396
+ }
1397
+
1398
+ // Update price analysis
1399
+ function updatePriceAnalysis(analytics) {
1400
+ const container = document.getElementById('priceAnalysis');
1401
+ const priceData = analytics.price_preferences || {};
1402
+
1403
+ container.innerHTML = `
1404
+ <div class="metric-item">
1405
+ <span class="metric-label">Average Price</span>
1406
+ <span class="metric-value">${formatPrice(priceData.avg_price || 0)}</span>
1407
+ </div>
1408
+ <div class="metric-item">
1409
+ <span class="metric-label">Min Price</span>
1410
+ <span class="metric-value">${formatPrice(priceData.min_price || 0)}</span>
1411
+ </div>
1412
+ <div class="metric-item">
1413
+ <span class="metric-label">Max Price</span>
1414
+ <span class="metric-value">${formatPrice(priceData.max_price || 0)}</span>
1415
+ </div>
1416
+ <div class="metric-item">
1417
+ <span class="metric-label">Price Range</span>
1418
+ <span class="metric-value">${formatPrice(priceData.price_range || 0)}</span>
1419
+ </div>
1420
+ `;
1421
+ }
1422
+
1423
+ // Update conversion analysis
1424
+ function updateConversionAnalysis(analytics) {
1425
+ const container = document.getElementById('conversionAnalysis');
1426
+ const conversionData = analytics.conversion_probability || {};
1427
+
1428
+ container.innerHTML = `
1429
+ <div class="metric-item">
1430
+ <span class="metric-label">Conversion Probability</span>
1431
+ <span class="metric-value">${Math.round(conversionData.final_probability || 0)}%</span>
1432
+ </div>
1433
+ <div class="metric-item">
1434
+ <span class="metric-label">Confidence Level</span>
1435
+ <span class="metric-value">${conversionData.confidence_level || 'Unknown'}</span>
1436
+ </div>
1437
+ <div class="metric-item">
1438
+ <span class="metric-label">Opportunity Score</span>
1439
+ <span class="metric-value">${Math.round(analytics.opportunity_score || 0)}</span>
1440
+ </div>
1441
+ <div class="metric-item">
1442
+ <span class="metric-label">Risk Level</span>
1443
+ <span class="metric-value">${analytics.risk_assessment?.risk_level || 'Unknown'}</span>
1444
+ </div>
1445
+ `;
1446
+ }
1447
+
1448
+ // Utility functions
1449
+ function formatPrice(price) {
1450
+ return `β‚Ή${price.toLocaleString('en-IN')}`;
1451
+ }
1452
+
1453
+ function formatDuration(seconds) {
1454
+ const hours = Math.floor(seconds / 3600);
1455
+ const minutes = Math.floor((seconds % 3600) / 60);
1456
+ return `${hours}h ${minutes}m`;
1457
+ }
1458
+
1459
+ function showLoading(text, subtext) {
1460
+ document.getElementById('loadingSection').style.display = 'block';
1461
+ document.getElementById('loadingText').textContent = text;
1462
+ document.getElementById('loadingSubtext').textContent = subtext;
1463
+ document.getElementById('searchBtn').disabled = true;
1464
+ document.getElementById('aiAnalyzeBtn').disabled = true;
1465
+ }
1466
+
1467
+ function hideLoading() {
1468
+ document.getElementById('loadingSection').style.display = 'none';
1469
+ document.getElementById('searchBtn').disabled = false;
1470
+ if (currentData) {
1471
+ document.getElementById('aiAnalyzeBtn').disabled = false;
1472
+ }
1473
+ }
1474
+
1475
+ function showError(message) {
1476
+ const errorSection = document.getElementById('errorSection');
1477
+ errorSection.textContent = message;
1478
+ errorSection.style.display = 'block';
1479
+ }
1480
+
1481
+ function hideError() {
1482
+ document.getElementById('errorSection').style.display = 'none';
1483
+ }
1484
+
1485
+ function showSuccess(message) {
1486
+ const successSection = document.getElementById('successSection');
1487
+ successSection.textContent = message;
1488
+ successSection.style.display = 'block';
1489
+ setTimeout(() => {
1490
+ successSection.style.display = 'none';
1491
+ }, 5000);
1492
+ }
1493
+
1494
+ function hideSuccess() {
1495
+ document.getElementById('successSection').style.display = 'none';
1496
+ }
1497
+
1498
+ function showResults() {
1499
+ document.getElementById('resultsSection').style.display = 'block';
1500
+ }
1501
+
1502
+ function hideResults() {
1503
+ document.getElementById('resultsSection').style.display = 'none';
1504
+ }
1505
+
1506
+ function showEmailSection() {
1507
+ document.getElementById('emailSection').style.display = 'block';
1508
+ }
1509
+
1510
+ function enableAIButton() {
1511
+ const aiBtn = document.getElementById('aiAnalyzeBtn');
1512
+ aiBtn.disabled = false;
1513
+ }
1514
+
1515
+ // Test email system
1516
+ async function testEmailSystem() {
1517
+ const testBtn = document.getElementById('testEmailBtn');
1518
+ const statusDiv = document.getElementById('testEmailStatus');
1519
+
1520
+ testBtn.disabled = true;
1521
+ testBtn.textContent = 'πŸ§ͺ Testing...';
1522
+ statusDiv.innerHTML = '<div class="status-indicator status-processing">πŸ”„ Testing email system...</div>';
1523
+ try {
1524
+ const response = await fetch('/api/test-email', {
1525
+ method: 'POST',
1526
+ headers: {
1527
+ 'Content-Type': 'application/json'
1528
+ }
1529
+ });
1530
+ const data = await response.json();
1531
+ if (data.success) {
1532
+ statusDiv.innerHTML = '<div class="status-indicator status-completed">βœ… Email system test successful! Check your email.</div>';
1533
+ showSuccess('πŸ§ͺ Email system test completed successfully!');
1534
+ } else {
1535
+ statusDiv.innerHTML = `<div class="status-indicator status-failed">❌ ${data.error}</div>`;
1536
+ }
1537
+ } catch (error) {
1538
+ statusDiv.innerHTML = `<div class="status-indicator status-failed">❌ Error: ${error.message}</div>`;
1539
+ } finally {
1540
+ testBtn.disabled = false;
1541
+ testBtn.textContent = 'πŸ§ͺ Test Email System';
1542
+ }
1543
+ }
1544
+
1545
+ // Test automated email
1546
+ async function testAutomatedEmail(customerId) {
1547
+ const testBtn = document.getElementById('testAutomatedEmailBtn');
1548
+ const statusDiv = document.getElementById('testEmailStatus');
1549
+
1550
+ testBtn.disabled = true;
1551
+ testBtn.textContent = 'πŸ€– Testing...';
1552
+ statusDiv.innerHTML = '<div class="status-indicator status-processing">πŸ”„ Testing automated email...</div>';
1553
+ try {
1554
+ const response = await fetch('/api/automated-email', {
1555
+ method: 'POST',
1556
+ headers: {
1557
+ 'Content-Type': 'application/json'
1558
+ },
1559
+ body: JSON.stringify({
1560
+ customer_id: customerId,
1561
+ email_type: 'behavioral_trigger',
1562
+ recipient_email: 'sameermujahid7777@gmail.com'
1563
+ })
1564
+ });
1565
+ const data = await response.json();
1566
+ if (data.success) {
1567
+ statusDiv.innerHTML = '<div class="status-indicator status-completed">βœ… Automated email test successful! Check your email.</div>';
1568
+ showSuccess('πŸ€– Automated email test completed successfully!');
1569
+ } else {
1570
+ statusDiv.innerHTML = `<div class="status-indicator status-failed">❌ ${data.error}</div>`;
1571
+ }
1572
+ } catch (error) {
1573
+ statusDiv.innerHTML = `<div class="status-indicator status-failed">❌ Error: ${error.message}</div>`;
1574
+ } finally {
1575
+ testBtn.disabled = false;
1576
+ testBtn.textContent = 'πŸ€– Test Automated Email';
1577
+ }
1578
+ }
1579
+
1580
+ // Enable automated emails
1581
+ async function enableAutomatedEmails() {
1582
+ const enableBtn = document.getElementById('enableAutoEmailBtn');
1583
+ const statusDiv = document.getElementById('autoEmailStatus');
1584
+
1585
+ enableBtn.disabled = true;
1586
+ enableBtn.textContent = 'βœ… Enabling...';
1587
+ statusDiv.innerHTML = '<div class="status-indicator status-processing">πŸ”„ Enabling automated emails...</div>';
1588
+ try {
1589
+ // This would typically configure the automation system
1590
+ // For now, we'll just show a success message
1591
+ setTimeout(() => {
1592
+ statusDiv.innerHTML = '<div class="status-indicator status-completed">βœ… Automated emails enabled successfully!</div>';
1593
+ enableBtn.textContent = 'βœ… Automated Emails Enabled';
1594
+ showSuccess('πŸ€– Automated emails enabled successfully!');
1595
+ }, 2000);
1596
+ } catch (error) {
1597
+ statusDiv.innerHTML = `<div class="status-indicator status-failed">❌ Error: ${error.message}</div>`;
1598
+ enableBtn.disabled = false;
1599
+ enableBtn.textContent = 'βœ… Enable Automated Emails';
1600
+ }
1601
+ }
1602
+
1603
+ // Configure email triggers
1604
+ function configureEmailTriggers() {
1605
+ const statusDiv = document.getElementById('autoEmailStatus');
1606
+
1607
+ statusDiv.innerHTML = `
1608
+ <div class="alert alert-light shadow-sm rounded p-3">
1609
+ <h4 class="mb-3"><i class="fas fa-cog"></i> Email Trigger Configuration</h4>
1610
+ <p><strong>Current Triggers:</strong></p>
1611
+ <ul>
1612
+ <li><i class="fas fa-clock"></i> Peak Time Emails (9:00 AM daily)</li>
1613
+ <li><i class="fas fa-chart-line"></i> Engagement Follow-up (2:00 PM daily)</li>
1614
+ <li><i class="fas fa-bullseye"></i> Behavioral Triggers (6:00 PM daily)</li>
1615
+ <li><i class="fas fa-fire"></i> Hot Lead Alerts (Immediate)</li>
1616
+ <li><i class="fas fa-sun"></i> Warm Lead Nurturing (24 hours)</li>
1617
+ </ul>
1618
+ <p><strong>Recipient:</strong> sameermujahid7777@gmail.com</p>
1619
+ <p class="text-muted small mb-0"><em>All emails are automatically sent to the configured recipient address.</em></p>
1620
+ </div>
1621
+ `;
1622
+ }
1623
+
1624
+ // Email functions
1625
+ async function testEmailSimple() {
1626
+ const resultDiv = document.getElementById('emailTestResult');
1627
+
1628
+ resultDiv.innerHTML = '<div class="alert alert-info"><i class="fas fa-vial"></i> Testing SendGrid email (simple GET)...</div>';
1629
+
1630
+ try {
1631
+ const response = await fetch('/api/test-email-simple');
1632
+ const data = await response.json();
1633
+
1634
+ if (data.success) {
1635
+ resultDiv.innerHTML = `
1636
+ <div class="alert alert-success">
1637
+ <h6><i class="fas fa-check-circle"></i> Simple Email Test Successful!</h6>
1638
+ <p><strong>Recipient:</strong> ${data.recipient}</p>
1639
+ <p><strong>Subject:</strong> ${data.subject}</p>
1640
+ <p><strong>Message:</strong> ${data.message}</p>
1641
+ <p><strong>Timestamp:</strong> ${new Date(data.timestamp).toLocaleString()}</p>
1642
+ </div>
1643
+ `;
1644
+ showSuccess('Simple SendGrid email test successful!');
1645
+ } else {
1646
+ resultDiv.innerHTML = `
1647
+ <div class="alert alert-danger">
1648
+ <h6><i class="fas fa-times-circle"></i> Simple Email Test Failed</h6>
1649
+ <p><strong>Error:</strong> ${data.error}</p>
1650
+ <p><strong>Timestamp:</strong> ${new Date(data.timestamp || Date.now()).toLocaleString()}</p>
1651
+ </div>
1652
+ `;
1653
+ showError('Simple email test failed: ' + data.error);
1654
+ }
1655
+ } catch (error) {
1656
+ resultDiv.innerHTML = `
1657
+ <div class="alert alert-danger">
1658
+ <h6><i class="fas fa-times-circle"></i> Simple Email Test Error</h6>
1659
+ <p><strong>Error:</strong> ${error.message}</p>
1660
+ </div>
1661
+ `;
1662
+ showError('Simple email test error: ' + error.message);
1663
+ }
1664
+ }
1665
+
1666
+ async function testEmail() {
1667
+ const email = document.getElementById('testEmail').value;
1668
+ const resultDiv = document.getElementById('emailTestResult');
1669
+
1670
+ if (!email) {
1671
+ showError('Please enter an email address');
1672
+ return;
1673
+ }
1674
+
1675
+ resultDiv.innerHTML = '<div class="alert alert-info"><i class="fas fa-vial"></i> Testing SendGrid email...</div>';
1676
+
1677
+ // Debug the request data
1678
+ const requestData = { email: email };
1679
+ console.log('Sending email test request:', requestData);
1680
+
1681
+ try {
1682
+ const response = await fetch('/api/test-email', {
1683
+ method: 'POST',
1684
+ headers: {
1685
+ 'Content-Type': 'application/json',
1686
+ },
1687
+ body: JSON.stringify({
1688
+ email: email
1689
+ })
1690
+ });
1691
+
1692
+ console.log('Response status:', response.status);
1693
+ console.log('Response headers:', response.headers);
1694
+
1695
+ const data = await response.json();
1696
+ console.log('Response data:', data);
1697
+
1698
+ if (data.success) {
1699
+ resultDiv.innerHTML = `
1700
+ <div class="alert alert-success">
1701
+ <h6><i class="fas fa-check-circle"></i> Email Test Successful!</h6>
1702
+ <p><strong>Recipient:</strong> ${data.recipient}</p>
1703
+ <p><strong>Subject:</strong> ${data.subject}</p>
1704
+ <p><strong>Message:</strong> ${data.message}</p>
1705
+ <p><strong>Timestamp:</strong> ${new Date(data.timestamp).toLocaleString()}</p>
1706
+ </div>
1707
+ `;
1708
+ showSuccess('SendGrid email test successful!');
1709
+ } else {
1710
+ resultDiv.innerHTML = `
1711
+ <div class="alert alert-danger">
1712
+ <h6><i class="fas fa-times-circle"></i> Email Test Failed</h6>
1713
+ <p><strong>Error:</strong> ${data.error}</p>
1714
+ <p><strong>Timestamp:</strong> ${new Date(data.timestamp || Date.now()).toLocaleString()}</p>
1715
+ </div>
1716
+ `;
1717
+ showError('Email test failed: ' + data.error);
1718
+ }
1719
+ } catch (error) {
1720
+ console.error('Email test error:', error);
1721
+ resultDiv.innerHTML = `
1722
+ <div class="alert alert-danger">
1723
+ <h6><i class="fas fa-times-circle"></i> Email Test Error</h6>
1724
+ <p><strong>Error:</strong> ${error.message}</p>
1725
+ <p><strong>Request Data:</strong> ${JSON.stringify(requestData)}</p>
1726
+ </div>
1727
+ `;
1728
+ showError('Email test error: ' + error.message);
1729
+ }
1730
+ }
1731
+
1732
+ async function sendAutomatedEmail() {
1733
+ const email = document.getElementById('userEmail').value;
1734
+ const sendBtn = document.getElementById('sendEmailBtn');
1735
+ const statusDiv = document.getElementById('emailStatus');
1736
+
1737
+ if (!email) {
1738
+ showError('Please enter an email address for automated email sending.');
1739
+ return;
1740
+ }
1741
+ sendBtn.disabled = true;
1742
+ sendBtn.textContent = 'πŸ“§ Sending...';
1743
+ statusDiv.innerHTML = '<div class="status-indicator status-processing"><i class="fas fa-spinner fa-spin"></i> Sending automated email...</div>';
1744
+ try {
1745
+ const response = await fetch(`/api/automated-email`, {
1746
+ method: 'POST',
1747
+ headers: {
1748
+ 'Content-Type': 'application/json'
1749
+ },
1750
+ body: JSON.stringify({
1751
+ customer_id: currentCustomerId,
1752
+ email_type: 'behavioral_trigger',
1753
+ recipient_email: email
1754
+ })
1755
+ });
1756
+ const data = await response.json();
1757
+ if (data.success) {
1758
+ statusDiv.innerHTML = '<div class="status-indicator status-completed"><i class="fas fa-check-circle"></i> Automated email sent successfully!</div>';
1759
+ showSuccess('Automated email sent successfully!');
1760
+ } else {
1761
+ statusDiv.innerHTML = `<div class="status-indicator status-failed"><i class="fas fa-times-circle"></i> ${data.error}</div>`;
1762
+ showError('Failed to send automated email: ' + data.error);
1763
+ }
1764
+ } catch (error) {
1765
+ statusDiv.innerHTML = `<div class="status-indicator status-failed"><i class="fas fa-times-circle"></i> Error: ${error.message}</div>`;
1766
+ showError('Error sending automated email: ' + error.message);
1767
+ } finally {
1768
+ sendBtn.disabled = false;
1769
+ sendBtn.textContent = '<i class="fas fa-envelope"></i> Send AI Recommendations';
1770
+ }
1771
+ }
1772
+
1773
+ // Automated Email Analysis Functions
1774
+ async function analyzeEmailTriggers() {
1775
+ const customerId = document.getElementById('customerId').value;
1776
+ if (!customerId) {
1777
+ showError('Please enter a Customer ID');
1778
+ return;
1779
+ }
1780
+
1781
+ currentCustomerId = parseInt(customerId);
1782
+
1783
+ const aiSummaryDiv = document.getElementById('aiAnalysisSummary');
1784
+ const aiContentDiv = document.getElementById('aiAnalysisContent');
1785
+ const triggersDisplayDiv = document.getElementById('emailTriggersDisplay');
1786
+ const triggersContentDiv = document.getElementById('emailTriggersContent');
1787
+ const resultDiv = document.getElementById('automatedEmailResult');
1788
+
1789
+ // Show loading
1790
+ aiSummaryDiv.style.display = 'none';
1791
+ triggersDisplayDiv.style.display = 'none';
1792
+ resultDiv.innerHTML = '<div class="alert alert-info"><i class="fas fa-search"></i> Analyzing email triggers for customer ' + customerId + '...</div>';
1793
+
1794
+ try {
1795
+ const response = await fetch(`/api/automated-email-analysis/${currentCustomerId}`);
1796
+ const data = await response.json();
1797
+
1798
+ if (data.success) {
1799
+ // Display AI Analysis Summary
1800
+ let aiSummaryHtml = '';
1801
+ if (data.analysis_summary) {
1802
+ const summary = data.analysis_summary;
1803
+ aiSummaryHtml = `
1804
+ <div class="row">
1805
+ <div class="col-md-3">
1806
+ <strong>Engagement Level:</strong><br>
1807
+ <span class="badge bg-${summary.engagement_level === 'High' ? 'success' : summary.engagement_level === 'Medium' ? 'warning' : 'secondary'}">${summary.engagement_level}</span>
1808
+ </div>
1809
+ <div class="col-md-3">
1810
+ <strong>Lead Score:</strong><br>
1811
+ <span class="badge bg-primary">${summary.lead_score}/100</span>
1812
+ </div>
1813
+ <div class="col-md-3">
1814
+ <strong>Properties Viewed:</strong><br>
1815
+ <span class="badge bg-info">${summary.total_properties_viewed}</span>
1816
+ </div>
1817
+ <div class="col-md-3">
1818
+ <strong>Price Range:</strong><br>
1819
+ <span class="badge bg-warning">${summary.average_price_range}</span>
1820
+ </div>
1821
+ </div>
1822
+ <div class="row mt-2">
1823
+ <div class="col-md-6">
1824
+ <strong>Preferred Locations:</strong> ${summary.preferred_locations} areas
1825
+ </div>
1826
+ <div class="col-md-6">
1827
+ <strong>AI Personality:</strong> ${data.ai_insights?.personality_type || 'Data-driven'}
1828
+ </div>
1829
+ </div>
1830
+ `;
1831
+ }
1832
+ aiContentDiv.innerHTML = aiSummaryHtml;
1833
+ aiSummaryDiv.style.display = 'block';
1834
+
1835
+ // Display Email Triggers
1836
+ let triggersHtml = '';
1837
+ if (data.triggers && data.triggers.length > 0) {
1838
+ triggersHtml = `
1839
+ <div class="alert alert-success">
1840
+ <h6><i class="fas fa-check-circle"></i> Found ${data.total_triggers} Email Triggers!</h6>
1841
+ <p>Based on AI analysis of customer behavior, the following automated emails would be sent:</p>
1842
+ </div>
1843
+ <div class="row">
1844
+ `;
1845
+
1846
+ data.triggers.forEach((trigger, index) => {
1847
+ const priorityColor = trigger.priority === 'high' ? 'danger' :
1848
+ trigger.priority === 'medium' ? 'warning' : 'info';
1849
+ const priorityIcon = trigger.priority === 'high' ? 'πŸ”₯' :
1850
+ trigger.priority === 'medium' ? '⚑' : 'πŸ“§';
1851
+
1852
+ triggersHtml += `
1853
+ <div class="col-md-6 mb-3">
1854
+ <div class="card border-${priorityColor} h-100 shadow-sm">
1855
+ <div class="card-header bg-${priorityColor} text-white">
1856
+ <strong>${priorityIcon} ${trigger.trigger_type.replace('_', ' ').toUpperCase()}</strong>
1857
+ <span class="badge bg-light text-dark float-end">${trigger.priority.toUpperCase()}</span>
1858
+ </div>
1859
+ <div class="card-body">
1860
+ <h6 class="card-title">Why this email would be sent:</h6>
1861
+ <p class="card-text"><strong>Condition:</strong> ${trigger.condition}</p>
1862
+ <p class="card-text"><strong>Reason:</strong> ${trigger.reason}</p>
1863
+ <p class="card-text"><strong>Recommendations:</strong> ${trigger.recommendation_count} properties</p>
1864
+ <div class="mt-2">
1865
+ <small class="text-muted">
1866
+ <i class="fas fa-info-circle"></i> This email would be automatically triggered based on customer behavior analysis.
1867
+ </small>
1868
+ </div>
1869
+ </div>
1870
+ </div>
1871
+ </div>
1872
+ `;
1873
+ });
1874
+
1875
+ triggersHtml += `
1876
+ </div>
1877
+ <div class="alert alert-info mt-3">
1878
+ <h6><i class="fas fa-clipboard-list"></i> Email Trigger Summary:</h6>
1879
+ <ul>
1880
+ <li><strong>High Priority:</strong> ${data.triggers.filter(t => t.priority === 'high').length} emails</li>
1881
+ <li><strong>Medium Priority:</strong> ${data.triggers.filter(t => t.priority === 'medium').length} emails</li>
1882
+ <li><strong>Total Triggers:</strong> ${data.total_triggers} emails</li>
1883
+ </ul>
1884
+ <p class="mb-0"><strong>Ready to test?</strong> Use the "Test All Emails" button above to send these triggered emails.</p>
1885
+ </div>
1886
+ `;
1887
+ } else {
1888
+ triggersHtml = `
1889
+ <div class="alert alert-warning">
1890
+ <h6><i class="fas fa-exclamation-triangle"></i> No Email Triggers Found</h6>
1891
+ <p>No automated email triggers were detected for this customer based on current behavior patterns.</p>
1892
+ <p><strong>Possible reasons:</strong></p>
1893
+ <ul>
1894
+ <li>Customer has low engagement level</li>
1895
+ <li>Insufficient property viewing data</li>
1896
+ <li>Customer behavior doesn't match trigger conditions</li>
1897
+ </ul>
1898
+ <p class="mb-0"><strong>Note:</strong> The AI system analyzes customer behavior to determine when automated emails should be sent. No triggers mean the customer doesn't currently meet the criteria for automated emails.</p>
1899
+ </div>
1900
+ `;
1901
+ }
1902
+
1903
+ triggersContentDiv.innerHTML = triggersHtml;
1904
+ triggersDisplayDiv.style.display = 'block';
1905
+
1906
+ resultDiv.innerHTML = `
1907
+ <div class="alert alert-success">
1908
+ <h6><i class="fas fa-check-circle"></i> Email Trigger Analysis Complete!</h6>
1909
+ <p><strong>Customer ID:</strong> ${data.customer_id}</p>
1910
+ <p><strong>Analysis Method:</strong> AI-powered behavioral analysis</p>
1911
+ <p><strong>Total Triggers Found:</strong> ${data.total_triggers}</p>
1912
+ <p><strong>Analysis Timestamp:</strong> ${new Date().toLocaleString()}</p>
1913
+ </div>
1914
+ `;
1915
+ showSuccess('Email trigger analysis completed! Check the details above.');
1916
+ } else {
1917
+ resultDiv.innerHTML = `
1918
+ <div class="alert alert-danger">
1919
+ <h6><i class="fas fa-times-circle"></i> Analysis Failed</h6>
1920
+ <p><strong>Error:</strong> ${data.error}</p>
1921
+ </div>
1922
+ `;
1923
+ showError('Analysis failed: ' + data.error);
1924
+ }
1925
+ } catch (error) {
1926
+ resultDiv.innerHTML = `
1927
+ <div class="alert alert-danger">
1928
+ <h6><i class="fas fa-times-circle"></i> Analysis Error</h6>
1929
+ <p><strong>Error:</strong> ${error.message}</p>
1930
+ </div>
1931
+ `;
1932
+ showError('Analysis error: ' + error.message);
1933
+ }
1934
+ }
1935
+
1936
+ async function testAutomatedEmails() {
1937
+ const customerId = document.getElementById('customerId').value;
1938
+ const testEmail = document.getElementById('automatedEmailTest').value;
1939
+
1940
+ if (!customerId) {
1941
+ showError('Please enter a Customer ID');
1942
+ return;
1943
+ }
1944
+
1945
+ if (!testEmail) {
1946
+ showError('Please enter a test email address');
1947
+ return;
1948
+ }
1949
+
1950
+ currentCustomerId = parseInt(customerId);
1951
+
1952
+ const resultDiv = document.getElementById('automatedEmailResult');
1953
+ resultDiv.innerHTML = '<div class="alert alert-info"><i class="fas fa-paper-plane"></i> Testing automated emails for customer ' + customerId + '...</div>';
1954
+
1955
+ try {
1956
+ const response = await fetch(`/api/test-automated-emails/${currentCustomerId}`, {
1957
+ method: 'POST',
1958
+ headers: {
1959
+ 'Content-Type': 'application/json'
1960
+ },
1961
+ body: JSON.stringify({
1962
+ recipient_email: testEmail
1963
+ })
1964
+ });
1965
+
1966
+ const data = await response.json();
1967
+
1968
+ if (data.success) {
1969
+ resultDiv.innerHTML = `
1970
+ <div class="alert alert-success">
1971
+ <h6><i class="fas fa-check-circle"></i> Automated Email Test Successful!</h6>
1972
+ <p><strong>Customer ID:</strong> ${data.customer_id}</p>
1973
+ <p><strong>Emails Sent:</strong> ${data.emails_sent}</p>
1974
+ <p><strong>Success Rate:</strong> ${data.success_rate}%</p>
1975
+ <p><strong>Test Email:</strong> ${testEmail}</p>
1976
+ <p><strong>Timestamp:</strong> ${new Date().toLocaleString()}</p>
1977
+ </div>
1978
+ `;
1979
+ showSuccess(`Successfully sent ${data.emails_sent} automated emails to ${testEmail}`);
1980
+ } else {
1981
+ resultDiv.innerHTML = `
1982
+ <div class="alert alert-danger">
1983
+ <h6><i class="fas fa-times-circle"></i> Automated Email Test Failed</h6>
1984
+ <p><strong>Error:</strong> ${data.error}</p>
1985
+ </div>
1986
+ `;
1987
+ showError('Automated email test failed: ' + data.error);
1988
+ }
1989
+ } catch (error) {
1990
+ resultDiv.innerHTML = `
1991
+ <div class="alert alert-danger">
1992
+ <h6><i class="fas fa-times-circle"></i> Automated Email Test Error</h6>
1993
+ <p><strong>Error:</strong> ${error.message}</p>
1994
+ </div>
1995
+ `;
1996
+ showError('Automated email test error: ' + error.message);
1997
+ }
1998
+ }
1999
+
2000
+ // Property Database Management Functions
2001
+ async function checkDatabaseStatus() {
2002
+ const statusDiv = document.getElementById('databaseStatus');
2003
+ statusDiv.innerHTML = '<p><i class="fas fa-spinner fa-spin"></i> Checking database status...</p>';
2004
+
2005
+ try {
2006
+ const response = await fetch('/api/fetch-all-properties');
2007
+ const data = await response.json();
2008
+
2009
+ if (data.success) {
2010
+ statusDiv.innerHTML = `
2011
+ <div class="row">
2012
+ <div class="col-md-3">
2013
+ <strong>Properties in Database:</strong><br>
2014
+ <span class="badge bg-success">${data.total_properties}</span>
2015
+ </div>
2016
+ <div class="col-md-3">
2017
+ <strong>Database Status:</strong><br>
2018
+ <span class="badge bg-success">Active</span>
2019
+ </div>
2020
+ <div class="col-md-3">
2021
+ <strong>Last Updated:</strong><br>
2022
+ <span class="badge bg-info">${new Date(data.timestamp).toLocaleString()}</span>
2023
+ </div>
2024
+ <div class="col-md-3">
2025
+ <strong>Storage:</strong><br>
2026
+ <span class="badge bg-warning">ChromaDB</span>
2027
+ </div>
2028
+ </div>
2029
+ <p class="mt-2 mb-0"><small class="text-muted">Database is ready for AI analysis and email generation.</small></p>
2030
+ `;
2031
+ } else {
2032
+ statusDiv.innerHTML = `
2033
+ <div class="alert alert-warning">
2034
+ <strong>Database Status:</strong> ${data.error || 'Unknown error'}
2035
+ </div>
2036
+ `;
2037
+ }
2038
+ } catch (error) {
2039
+ statusDiv.innerHTML = `
2040
+ <div class="alert alert-danger">
2041
+ <strong>Error:</strong> ${error.message}
2042
+ </div>
2043
+ `;
2044
+ }
2045
+ }
2046
+
2047
+ async function fetchAllProperties() {
2048
+ const resultDiv = document.getElementById('fetchResult');
2049
+ resultDiv.innerHTML = '<div class="alert alert-info"><i class="fas fa-rocket"></i> Fetching all 500+ properties using parallel processing...</div>';
2050
+
2051
+ const workers = document.getElementById('workerCount').value || 10;
2052
+ const pageSize = document.getElementById('pageSize').value || 500;
2053
+ const maxPages = document.getElementById('maxPages').value || 10;
2054
+
2055
+ const startTime = Date.now();
2056
+
2057
+ try {
2058
+ const response = await fetch(`/api/fetch-all-properties?workers=${workers}&page_size=${pageSize}&max_pages=${maxPages}`);
2059
+ const data = await response.json();
2060
+ const endTime = Date.now();
2061
+ const duration = (endTime - startTime) / 1000;
2062
+
2063
+ if (data.success) {
2064
+ resultDiv.innerHTML = `
2065
+ <div class="alert alert-success">
2066
+ <h6><i class="fas fa-check-circle"></i> Successfully Fetched All Properties!</h6>
2067
+ <p><strong>Total Properties:</strong> ${data.total_properties}</p>
2068
+ <p><strong>Parameters Used:</strong></p>
2069
+ <ul>
2070
+ <li>Workers: ${data.parameters_used.workers}</li>
2071
+ <li>Page Size: ${data.parameters_used.page_size}</li>
2072
+ <li>Max Pages: ${data.parameters_used.max_pages}</li>
2073
+ </ul>
2074
+ <p><strong>Duration:</strong> ${duration.toFixed(2)} seconds</p>
2075
+ <p><strong>Timestamp:</strong> ${new Date(data.timestamp).toLocaleString()}</p>
2076
+ </div>
2077
+ `;
2078
+ showSuccess(`Successfully fetched ${data.total_properties} properties in ${duration.toFixed(2)}s`);
2079
+
2080
+ // Refresh database status
2081
+ checkDatabaseStatus();
2082
+ } else {
2083
+ resultDiv.innerHTML = `
2084
+ <div class="alert alert-danger">
2085
+ <h6><i class="fas fa-times-circle"></i> Property Fetch Failed</h6>
2086
+ <p><strong>Error:</strong> ${data.error}</p>
2087
+ <p><strong>Duration:</strong> ${duration.toFixed(2)} seconds</p>
2088
+ </div>
2089
+ `;
2090
+ showError('Property fetch failed: ' + data.error);
2091
+ }
2092
+ } catch (error) {
2093
+ const endTime = Date.now();
2094
+ const duration = (endTime - startTime) / 1000;
2095
+
2096
+ resultDiv.innerHTML = `
2097
+ <div class="alert alert-danger">
2098
+ <h6><i class="fas fa-times-circle"></i> Property Fetch Error</h6>
2099
+ <p><strong>Error:</strong> ${error.message}</p>
2100
+ <p><strong>Duration:</strong> ${duration.toFixed(2)} seconds</p>
2101
+ </div>
2102
+ `;
2103
+ showError('Property fetch error: ' + error.message);
2104
+ }
2105
+ }
2106
+
2107
+ async function testParallelFetching() {
2108
+ const resultDiv = document.getElementById('fetchResult');
2109
+ resultDiv.innerHTML = '<div class="alert alert-info"><i class="fas fa-bolt"></i> Testing parallel fetching with default settings...</div>';
2110
+
2111
+ const startTime = Date.now();
2112
+
2113
+ try {
2114
+ const response = await fetch('/api/fetch-all-properties');
2115
+ const data = await response.json();
2116
+ const endTime = Date.now();
2117
+ const duration = (endTime - startTime) / 1000;
2118
+
2119
+ if (data.success) {
2120
+ resultDiv.innerHTML = `
2121
+ <div class="alert alert-success">
2122
+ <h6><i class="fas fa-check-circle"></i> Quick Test Successful!</h6>
2123
+ <p><strong>Total Properties:</strong> ${data.total_properties}</p>
2124
+ <p><strong>Duration:</strong> ${duration.toFixed(2)} seconds</p>
2125
+ </div>
2126
+ `;
2127
+ showSuccess(`Quick test completed: ${data.total_properties} properties in ${duration.toFixed(2)}s`);
2128
+
2129
+ // Refresh database status
2130
+ checkDatabaseStatus();
2131
+ } else {
2132
+ resultDiv.innerHTML = `
2133
+ <div class="alert alert-danger">
2134
+ <h6><i class="fas fa-times-circle"></i> Quick Test Failed</h6>
2135
+ <p><strong>Error:</strong> ${data.error}</p>
2136
+ </div>
2137
+ `;
2138
+ showError('Quick test failed: ' + data.error);
2139
+ }
2140
+ } catch (error) {
2141
+ resultDiv.innerHTML = `
2142
+ <div class="alert alert-danger">
2143
+ <h6><i class="fas fa-times-circle"></i> Quick Test Error</h6>
2144
+ <p><strong>Error:</strong> ${error.message}</p>
2145
+ </div>
2146
+ `;
2147
+ showError('Quick test error: ' + error.message);
2148
+ }
2149
+ }
2150
+ </script>
2151
+ </body>
2152
+ </html>
templates/templates/analytics.html ADDED
@@ -0,0 +1,919 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Lead Qualification Analytics Dashboard</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
9
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
10
+ <style>
11
+ body {
12
+ background-color: #f8f9fa;
13
+ padding-top: 2rem;
14
+ }
15
+ .header {
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ color: white;
18
+ padding: 2rem 0;
19
+ margin-bottom: 2rem;
20
+ }
21
+ .stats-card {
22
+ background: white;
23
+ padding: 1.5rem;
24
+ border-radius: 10px;
25
+ box-shadow: 0 0 10px rgba(0,0,0,0.1);
26
+ margin-bottom: 1rem;
27
+ text-align: center;
28
+ }
29
+ .stats-number {
30
+ font-size: 2.5rem;
31
+ font-weight: bold;
32
+ color: #007bff;
33
+ }
34
+ .stats-label {
35
+ color: #6c757d;
36
+ font-size: 1rem;
37
+ margin-top: 0.5rem;
38
+ }
39
+ .chart-container {
40
+ background: white;
41
+ padding: 2rem;
42
+ border-radius: 10px;
43
+ box-shadow: 0 0 10px rgba(0,0,0,0.1);
44
+ margin-bottom: 2rem;
45
+ }
46
+ .activity-list {
47
+ background: white;
48
+ padding: 2rem;
49
+ border-radius: 10px;
50
+ box-shadow: 0 0 10px rgba(0,0,0,0.1);
51
+ margin-bottom: 2rem;
52
+ }
53
+ .activity-item {
54
+ padding: 1rem;
55
+ border-bottom: 1px solid #e9ecef;
56
+ display: flex;
57
+ justify-content: space-between;
58
+ align-items: center;
59
+ }
60
+ .activity-item:last-child {
61
+ border-bottom: none;
62
+ }
63
+ .activity-icon {
64
+ width: 40px;
65
+ height: 40px;
66
+ border-radius: 50%;
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ color: white;
71
+ font-size: 1.2rem;
72
+ }
73
+ .activity-icon.click {
74
+ background: #28a745;
75
+ }
76
+ .activity-icon.search {
77
+ background: #007bff;
78
+ }
79
+ .activity-icon.visit {
80
+ background: #ffc107;
81
+ }
82
+ .activity-icon.email {
83
+ background: #6c757d;
84
+ }
85
+ .clear-data-btn {
86
+ background: #dc3545;
87
+ color: white;
88
+ border: none;
89
+ padding: 0.5rem 1rem;
90
+ border-radius: 5px;
91
+ cursor: pointer;
92
+ }
93
+ .clear-data-btn:hover {
94
+ background: #c82333;
95
+ }
96
+ .export-btn {
97
+ background: #28a745;
98
+ color: white;
99
+ border: none;
100
+ padding: 0.5rem 1rem;
101
+ border-radius: 5px;
102
+ cursor: pointer;
103
+ }
104
+ .export-btn:hover {
105
+ background: #218838;
106
+ }
107
+ .lead-score-card {
108
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
109
+ color: white;
110
+ padding: 2rem;
111
+ border-radius: 15px;
112
+ text-align: center;
113
+ margin-bottom: 2rem;
114
+ }
115
+ .lead-score-number {
116
+ font-size: 4rem;
117
+ font-weight: bold;
118
+ margin-bottom: 1rem;
119
+ }
120
+ .lead-score-label {
121
+ font-size: 1.2rem;
122
+ opacity: 0.9;
123
+ }
124
+ .lead-status {
125
+ padding: 0.5rem 1rem;
126
+ border-radius: 20px;
127
+ font-weight: bold;
128
+ margin-top: 1rem;
129
+ }
130
+ .lead-status.hot {
131
+ background: #dc3545;
132
+ }
133
+ .lead-status.warm {
134
+ background: #ffc107;
135
+ color: #212529;
136
+ }
137
+ .lead-status.cold {
138
+ background: #6c757d;
139
+ }
140
+ </style>
141
+ </head>
142
+ <body>
143
+ <div class="container-fluid">
144
+ <!-- Header -->
145
+ <div class="header">
146
+ <div class="container">
147
+ <div class="row align-items-center">
148
+ <div class="col-md-8">
149
+ <h1><i class="fas fa-chart-bar"></i> Lead Qualification Analytics Dashboard</h1>
150
+ <p class="mb-0">Track user behavior and lead qualification metrics</p>
151
+ </div>
152
+ <div class="col-md-4 text-end">
153
+ <a href="/" class="btn btn-outline-light me-2">
154
+ <i class="fas fa-home"></i> Home
155
+ </a>
156
+ <button class="btn btn-outline-light me-2" onclick="exportData()">
157
+ <i class="fas fa-download"></i> Export
158
+ </button>
159
+ <button class="btn btn-light" onclick="clearData()">
160
+ <i class="fas fa-trash"></i> Clear
161
+ </button>
162
+ </div>
163
+ </div>
164
+ </div>
165
+ </div>
166
+
167
+ <div class="container">
168
+ <!-- Lead Score Card -->
169
+ <div class="row mb-4">
170
+ <div class="col-md-12">
171
+ <div class="lead-score-card">
172
+ <div class="lead-score-number" id="leadScore">0</div>
173
+ <div class="lead-score-label">Lead Qualification Score</div>
174
+ <div class="lead-status" id="leadStatus">Cold</div>
175
+ </div>
176
+ </div>
177
+ </div>
178
+
179
+ <!-- Action Buttons -->
180
+ <div class="row mb-4">
181
+ <div class="col-md-6">
182
+ <button class="clear-data-btn me-2" onclick="clearData()">
183
+ <i class="fas fa-trash"></i> Clear All Data
184
+ </button>
185
+ <button class="export-btn" onclick="exportData()">
186
+ <i class="fas fa-download"></i> Export Data
187
+ </button>
188
+ </div>
189
+ <div class="col-md-6 text-end">
190
+ <small class="text-muted">Last updated: <span id="lastUpdated"></span></small>
191
+ </div>
192
+ </div>
193
+
194
+ <!-- Stats Cards -->
195
+ <div class="row mb-4">
196
+ <div class="col-md-2">
197
+ <div class="stats-card">
198
+ <div class="stats-number" id="totalClicks">0</div>
199
+ <div class="stats-label">Total Clicks</div>
200
+ </div>
201
+ </div>
202
+ <div class="col-md-2">
203
+ <div class="stats-card">
204
+ <div class="stats-number" id="totalSearches">0</div>
205
+ <div class="stats-label">Total Searches</div>
206
+ </div>
207
+ </div>
208
+ <div class="col-md-2">
209
+ <div class="stats-card">
210
+ <div class="stats-number" id="uniqueProperties">0</div>
211
+ <div class="stats-label">Unique Properties</div>
212
+ </div>
213
+ </div>
214
+ <div class="col-md-2">
215
+ <div class="stats-card">
216
+ <div class="stats-number" id="totalViewTime">0s</div>
217
+ <div class="stats-label">Total View Time</div>
218
+ </div>
219
+ </div>
220
+ <div class="col-md-2">
221
+ <div class="stats-card">
222
+ <div class="stats-number" id="avgViewDuration">0s</div>
223
+ <div class="stats-label">Avg View Duration</div>
224
+ </div>
225
+ </div>
226
+ <div class="col-md-2">
227
+ <div class="stats-card">
228
+ <div class="stats-number" id="visitRequests">0</div>
229
+ <div class="stats-label">Visit Requests</div>
230
+ </div>
231
+ </div>
232
+ </div>
233
+
234
+ <!-- Charts Row -->
235
+ <div class="row mb-4">
236
+ <div class="col-md-6">
237
+ <div class="chart-container">
238
+ <h5><i class="fas fa-chart-pie"></i> Property Type Preferences</h5>
239
+ <canvas id="propertyTypeChart"></canvas>
240
+ </div>
241
+ </div>
242
+ <div class="col-md-6">
243
+ <div class="chart-container">
244
+ <h5><i class="fas fa-chart-bar"></i> Price Range Preferences</h5>
245
+ <canvas id="priceRangeChart"></canvas>
246
+ </div>
247
+ </div>
248
+ </div>
249
+
250
+ <!-- Feature Preferences -->
251
+ <div class="row mb-4">
252
+ <div class="col-md-12">
253
+ <div class="chart-container">
254
+ <h5><i class="fas fa-star"></i> Feature Preferences</h5>
255
+ <canvas id="featureChart"></canvas>
256
+ </div>
257
+ </div>
258
+ </div>
259
+
260
+ <!-- Detailed View Analytics -->
261
+ <div class="row mb-4">
262
+ <div class="col-md-12">
263
+ <div class="chart-container">
264
+ <h5><i class="fas fa-clock"></i> Detailed View Analytics</h5>
265
+ <canvas id="viewDurationChart"></canvas>
266
+ </div>
267
+ </div>
268
+ </div>
269
+
270
+ <!-- Recent Activity -->
271
+ <div class="row">
272
+ <div class="col-md-12">
273
+ <div class="activity-list">
274
+ <h5><i class="fas fa-history"></i> Recent Activity</h5>
275
+ <div id="activityList">
276
+ <p class="text-muted">No activity recorded yet.</p>
277
+ </div>
278
+ </div>
279
+ </div>
280
+ </div>
281
+ </div>
282
+ </div>
283
+
284
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
285
+ <script>
286
+ class LeadAnalyticsDashboard {
287
+ constructor() {
288
+ this.trackingData = this.loadTrackingData();
289
+ this.init();
290
+ }
291
+
292
+ loadTrackingData() {
293
+ const data = localStorage.getItem('userTrackingData');
294
+ return data ? JSON.parse(data) : {
295
+ clickedProperties: [],
296
+ detailedViews: [],
297
+ searchHistory: [],
298
+ features: [],
299
+ priceRanges: [],
300
+ propertyTypes: []
301
+ };
302
+ }
303
+
304
+ init() {
305
+ this.updateStats();
306
+ this.createCharts();
307
+ this.displayRecentActivity();
308
+ this.updateLastUpdated();
309
+ this.updateLeadScore();
310
+ this.generateInsights();
311
+ }
312
+
313
+ updateStats() {
314
+ document.getElementById('totalClicks').textContent = this.trackingData.clickedProperties.length;
315
+ document.getElementById('totalSearches').textContent = this.trackingData.searchHistory.length;
316
+
317
+ // Unique properties clicked
318
+ const uniqueProperties = new Set(this.trackingData.clickedProperties.map(p => p.id)).size;
319
+ document.getElementById('uniqueProperties').textContent = uniqueProperties;
320
+
321
+ // Total view time
322
+ const totalViewTime = this.trackingData.detailedViews.reduce((sum, view) => sum + view.totalDuration, 0);
323
+ document.getElementById('totalViewTime').textContent = this.formatDuration(totalViewTime);
324
+
325
+ // Average view duration
326
+ if (this.trackingData.detailedViews.length > 0) {
327
+ const totalDuration = this.trackingData.detailedViews.reduce((sum, view) => sum + view.totalDuration, 0);
328
+ const totalViews = this.trackingData.detailedViews.reduce((sum, view) => sum + view.viewCount, 0);
329
+ const avgDuration = totalViews > 0 ? Math.round(totalDuration / totalViews) : 0;
330
+ document.getElementById('avgViewDuration').textContent = this.formatDuration(avgDuration);
331
+ }
332
+
333
+ // Visit requests (from localStorage or session)
334
+ const visitHistory = JSON.parse(localStorage.getItem('visitHistory') || '[]');
335
+ document.getElementById('visitRequests').textContent = visitHistory.length;
336
+ }
337
+
338
+ generateInsights() {
339
+ const insights = this.analyzeUserBehavior();
340
+ this.displayInsights(insights);
341
+ }
342
+
343
+ analyzeUserBehavior() {
344
+ const insights = {
345
+ preferredTypes: {},
346
+ preferredPriceRanges: {},
347
+ engagementLevel: 'low',
348
+ sessionDuration: 0,
349
+ topProperties: [],
350
+ recommendations: []
351
+ };
352
+
353
+ // Analyze property type preferences
354
+ this.trackingData.propertyTypes.forEach(type => {
355
+ insights.preferredTypes[type] = (insights.preferredTypes[type] || 0) + 1;
356
+ });
357
+
358
+ // Analyze price range preferences
359
+ this.trackingData.priceRanges.forEach(range => {
360
+ insights.preferredPriceRanges[range] = (insights.preferredPriceRanges[range] || 0) + 1;
361
+ });
362
+
363
+ // Calculate engagement level
364
+ const totalInteractions = this.trackingData.clickedProperties.length + this.trackingData.detailedViews.length;
365
+ const totalTime = this.trackingData.detailedViews.reduce((sum, view) => sum + view.totalDuration, 0);
366
+
367
+ if (totalInteractions >= 10 && totalTime >= 60) {
368
+ insights.engagementLevel = 'high';
369
+ } else if (totalInteractions >= 5 && totalTime >= 30) {
370
+ insights.engagementLevel = 'medium';
371
+ }
372
+
373
+ insights.sessionDuration = totalTime;
374
+
375
+ // Get top properties by engagement
376
+ insights.topProperties = this.trackingData.detailedViews
377
+ .sort((a, b) => b.totalDuration - a.totalDuration)
378
+ .slice(0, 3);
379
+
380
+ // Generate recommendations
381
+ insights.recommendations = this.generateRecommendations();
382
+
383
+ return insights;
384
+ }
385
+
386
+ generateRecommendations() {
387
+ const recommendations = [];
388
+ const insights = this.analyzeUserBehavior();
389
+
390
+ // Recommendation 1: Based on most viewed property type
391
+ const topType = Object.keys(insights.preferredTypes)
392
+ .sort((a, b) => insights.preferredTypes[b] - insights.preferredTypes[a])[0];
393
+
394
+ if (topType) {
395
+ recommendations.push({
396
+ type: 'property_type',
397
+ message: `You show strong interest in ${topType} properties. Consider exploring more ${topType} options.`,
398
+ priority: 'high'
399
+ });
400
+ }
401
+
402
+ // Recommendation 2: Based on price range
403
+ const topPriceRange = Object.keys(insights.preferredPriceRanges)
404
+ .sort((a, b) => insights.preferredPriceRanges[b] - insights.preferredPriceRanges[a])[0];
405
+
406
+ if (topPriceRange) {
407
+ recommendations.push({
408
+ type: 'price_range',
409
+ message: `Your preferred price range is ${this.formatPriceRange(topPriceRange)}. Focus on properties in this range.`,
410
+ priority: 'medium'
411
+ });
412
+ }
413
+
414
+ // Recommendation 3: Based on engagement
415
+ if (insights.engagementLevel === 'high') {
416
+ recommendations.push({
417
+ type: 'engagement',
418
+ message: 'High engagement detected! You\'re ready for personalized recommendations and follow-up.',
419
+ priority: 'high'
420
+ });
421
+ } else if (insights.engagementLevel === 'medium') {
422
+ recommendations.push({
423
+ type: 'engagement',
424
+ message: 'Good engagement level. Consider scheduling property visits for interested properties.',
425
+ priority: 'medium'
426
+ });
427
+ }
428
+
429
+ // Recommendation 4: Based on session duration
430
+ if (insights.sessionDuration < 30) {
431
+ recommendations.push({
432
+ type: 'duration',
433
+ message: 'Short session duration. Consider providing more detailed property information.',
434
+ priority: 'low'
435
+ });
436
+ }
437
+
438
+ return recommendations;
439
+ }
440
+
441
+ displayInsights(insights) {
442
+ // Create insights section if it doesn't exist
443
+ let insightsSection = document.getElementById('insightsSection');
444
+ if (!insightsSection) {
445
+ insightsSection = document.createElement('div');
446
+ insightsSection.id = 'insightsSection';
447
+ insightsSection.className = 'row mb-4';
448
+ insightsSection.innerHTML = `
449
+ <div class="col-md-12">
450
+ <div class="chart-container">
451
+ <h5><i class="fas fa-lightbulb"></i> AI-Powered Insights & Recommendations</h5>
452
+ <div id="insightsContent"></div>
453
+ </div>
454
+ </div>
455
+ `;
456
+
457
+ // Insert after the charts row
458
+ const chartsRow = document.querySelector('.row.mb-4:nth-of-type(3)');
459
+ if (chartsRow) {
460
+ chartsRow.parentNode.insertBefore(insightsSection, chartsRow.nextSibling);
461
+ }
462
+ }
463
+
464
+ const insightsContent = document.getElementById('insightsContent');
465
+ let insightsHTML = '';
466
+
467
+ // Display engagement summary
468
+ insightsHTML += `
469
+ <div class="row mb-3">
470
+ <div class="col-md-6">
471
+ <div class="alert alert-info">
472
+ <h6><i class="fas fa-chart-line"></i> Engagement Summary</h6>
473
+ <p><strong>Level:</strong> <span class="badge bg-${insights.engagementLevel === 'high' ? 'success' : insights.engagementLevel === 'medium' ? 'warning' : 'secondary'}">${insights.engagementLevel.toUpperCase()}</span></p>
474
+ <p><strong>Session Duration:</strong> ${this.formatDuration(insights.sessionDuration)}</p>
475
+ <p><strong>Total Interactions:</strong> ${this.trackingData.clickedProperties.length + this.trackingData.detailedViews.length}</p>
476
+ </div>
477
+ </div>
478
+ <div class="col-md-6">
479
+ <div class="alert alert-success">
480
+ <h6><i class="fas fa-star"></i> Top Property Types</h6>
481
+ ${Object.entries(insights.preferredTypes)
482
+ .sort(([,a], [,b]) => b - a)
483
+ .slice(0, 3)
484
+ .map(([type, count]) => `<p><strong>${type}:</strong> ${count} interactions</p>`)
485
+ .join('')}
486
+ </div>
487
+ </div>
488
+ </div>
489
+ `;
490
+
491
+ // Display recommendations
492
+ insightsHTML += `
493
+ <div class="row">
494
+ <div class="col-md-12">
495
+ <h6><i class="fas fa-recommendations"></i> AI Recommendations</h6>
496
+ ${insights.recommendations.map(rec => `
497
+ <div class="alert alert-${rec.priority === 'high' ? 'danger' : rec.priority === 'medium' ? 'warning' : 'info'}">
498
+ <i class="fas fa-${rec.type === 'property_type' ? 'home' : rec.type === 'price_range' ? 'money-bill' : rec.type === 'engagement' ? 'chart-line' : 'clock'}"></i>
499
+ ${rec.message}
500
+ </div>
501
+ `).join('')}
502
+ </div>
503
+ </div>
504
+ `;
505
+
506
+ // Display top properties
507
+ if (insights.topProperties.length > 0) {
508
+ insightsHTML += `
509
+ <div class="row mt-3">
510
+ <div class="col-md-12">
511
+ <h6><i class="fas fa-trophy"></i> Most Engaged Properties</h6>
512
+ <div class="row">
513
+ ${insights.topProperties.map(prop => `
514
+ <div class="col-md-4">
515
+ <div class="card">
516
+ <div class="card-body">
517
+ <h6 class="card-title">${prop.propertyName.substring(0, 30)}...</h6>
518
+ <p class="card-text">
519
+ <strong>Type:</strong> ${prop.propertyType}<br>
520
+ <strong>Price:</strong> β‚Ή${this.formatPrice(prop.price)}<br>
521
+ <strong>Duration:</strong> ${this.formatDuration(prop.totalDuration)}<br>
522
+ <strong>Views:</strong> ${prop.viewCount}
523
+ </p>
524
+ </div>
525
+ </div>
526
+ </div>
527
+ `).join('')}
528
+ </div>
529
+ </div>
530
+ </div>
531
+ `;
532
+ }
533
+
534
+ insightsContent.innerHTML = insightsHTML;
535
+ }
536
+
537
+ createCharts() {
538
+ this.createPropertyTypeChart();
539
+ this.createPriceRangeChart();
540
+ this.createFeatureChart();
541
+ this.createViewDurationChart();
542
+ }
543
+
544
+ createPropertyTypeChart() {
545
+ const ctx = document.getElementById('propertyTypeChart').getContext('2d');
546
+ const typeCounts = {};
547
+
548
+ this.trackingData.propertyTypes.forEach(type => {
549
+ typeCounts[type] = (typeCounts[type] || 0) + 1;
550
+ });
551
+
552
+ if (Object.keys(typeCounts).length === 0) {
553
+ ctx.canvas.style.display = 'none';
554
+ return;
555
+ }
556
+
557
+ new Chart(ctx, {
558
+ type: 'doughnut',
559
+ data: {
560
+ labels: Object.keys(typeCounts),
561
+ datasets: [{
562
+ data: Object.values(typeCounts),
563
+ backgroundColor: [
564
+ '#FF6384',
565
+ '#36A2EB',
566
+ '#FFCE56',
567
+ '#4BC0C0',
568
+ '#9966FF',
569
+ '#FF9F40'
570
+ ]
571
+ }]
572
+ },
573
+ options: {
574
+ responsive: true,
575
+ plugins: {
576
+ legend: {
577
+ position: 'bottom'
578
+ }
579
+ }
580
+ }
581
+ });
582
+ }
583
+
584
+ createPriceRangeChart() {
585
+ const ctx = document.getElementById('priceRangeChart').getContext('2d');
586
+ const rangeCounts = {};
587
+
588
+ this.trackingData.priceRanges.forEach(range => {
589
+ rangeCounts[range] = (rangeCounts[range] || 0) + 1;
590
+ });
591
+
592
+ if (Object.keys(rangeCounts).length === 0) {
593
+ ctx.canvas.style.display = 'none';
594
+ return;
595
+ }
596
+
597
+ const labels = {
598
+ '0-500000': 'Under β‚Ή5L',
599
+ '500000-1000000': 'β‚Ή5L - β‚Ή10L',
600
+ '1000000-2000000': 'β‚Ή10L - β‚Ή20L',
601
+ '2000000-5000000': 'β‚Ή20L - β‚Ή50L',
602
+ '5000000-10000000': 'β‚Ή50L - β‚Ή1Cr',
603
+ '10000000+': 'Above β‚Ή1Cr'
604
+ };
605
+
606
+ new Chart(ctx, {
607
+ type: 'bar',
608
+ data: {
609
+ labels: Object.keys(rangeCounts).map(key => labels[key] || key),
610
+ datasets: [{
611
+ label: 'Clicks',
612
+ data: Object.values(rangeCounts),
613
+ backgroundColor: '#007bff'
614
+ }]
615
+ },
616
+ options: {
617
+ responsive: true,
618
+ scales: {
619
+ y: {
620
+ beginAtZero: true
621
+ }
622
+ }
623
+ }
624
+ });
625
+ }
626
+
627
+ createFeatureChart() {
628
+ const ctx = document.getElementById('featureChart').getContext('2d');
629
+ const featureCounts = {};
630
+
631
+ this.trackingData.features.forEach(feature => {
632
+ featureCounts[feature] = (featureCounts[feature] || 0) + 1;
633
+ });
634
+
635
+ if (Object.keys(featureCounts).length === 0) {
636
+ ctx.canvas.style.display = 'none';
637
+ return;
638
+ }
639
+
640
+ // Sort by count and take top 10
641
+ const sortedFeatures = Object.entries(featureCounts)
642
+ .sort(([,a], [,b]) => b - a)
643
+ .slice(0, 10);
644
+
645
+ new Chart(ctx, {
646
+ type: 'horizontalBar',
647
+ data: {
648
+ labels: sortedFeatures.map(([feature]) => feature),
649
+ datasets: [{
650
+ label: 'Clicks',
651
+ data: sortedFeatures.map(([,count]) => count),
652
+ backgroundColor: '#28a745'
653
+ }]
654
+ },
655
+ options: {
656
+ responsive: true,
657
+ scales: {
658
+ x: {
659
+ beginAtZero: true
660
+ }
661
+ }
662
+ }
663
+ });
664
+ }
665
+
666
+ createViewDurationChart() {
667
+ const ctx = document.getElementById('viewDurationChart').getContext('2d');
668
+ const detailedViews = this.trackingData.detailedViews;
669
+
670
+ if (detailedViews.length === 0) {
671
+ ctx.canvas.style.display = 'none';
672
+ return;
673
+ }
674
+
675
+ // Sort by total duration and take top 10
676
+ const sortedViews = detailedViews
677
+ .sort((a, b) => b.totalDuration - a.totalDuration)
678
+ .slice(0, 10);
679
+
680
+ new Chart(ctx, {
681
+ type: 'bar',
682
+ data: {
683
+ labels: sortedViews.map(view => view.propertyName.substring(0, 20) + '...'),
684
+ datasets: [{
685
+ label: 'Total Duration (seconds)',
686
+ data: sortedViews.map(view => view.totalDuration),
687
+ backgroundColor: '#ff6384'
688
+ }, {
689
+ label: 'View Count',
690
+ data: sortedViews.map(view => view.viewCount),
691
+ backgroundColor: '#36a2eb'
692
+ }]
693
+ },
694
+ options: {
695
+ responsive: true,
696
+ scales: {
697
+ y: {
698
+ beginAtZero: true
699
+ }
700
+ }
701
+ }
702
+ });
703
+ }
704
+
705
+ displayRecentActivity() {
706
+ const container = document.getElementById('activityList');
707
+ const allActivities = [];
708
+
709
+ // Add clicked properties
710
+ this.trackingData.clickedProperties.forEach(prop => {
711
+ allActivities.push({
712
+ type: 'click',
713
+ text: `Clicked on ${prop.name}`,
714
+ timestamp: new Date(prop.timestamp),
715
+ price: prop.price,
716
+ propertyType: prop.type
717
+ });
718
+ });
719
+
720
+ // Add detailed views
721
+ this.trackingData.detailedViews.forEach(view => {
722
+ allActivities.push({
723
+ type: 'detailed_view',
724
+ text: `Viewed ${view.propertyName} for ${this.formatDuration(view.totalDuration)}`,
725
+ timestamp: new Date(view.lastViewed),
726
+ price: view.price,
727
+ propertyType: view.propertyType,
728
+ viewCount: view.viewCount,
729
+ totalDuration: view.totalDuration
730
+ });
731
+ });
732
+
733
+ // Add searches
734
+ this.trackingData.searchHistory.forEach(search => {
735
+ allActivities.push({
736
+ type: 'search',
737
+ text: `Searched for "${search.query}"`,
738
+ timestamp: new Date(search.timestamp),
739
+ filters: search.filters
740
+ });
741
+ });
742
+
743
+ // Add visit requests
744
+ const visitHistory = JSON.parse(localStorage.getItem('visitHistory') || '[]');
745
+ visitHistory.forEach(visit => {
746
+ allActivities.push({
747
+ type: 'visit',
748
+ text: `Requested visit for ${visit.propertyName}`,
749
+ timestamp: new Date(visit.timestamp),
750
+ propertyType: visit.propertyType
751
+ });
752
+ });
753
+
754
+ // Sort by timestamp (most recent first)
755
+ allActivities.sort((a, b) => b.timestamp - a.timestamp);
756
+
757
+ if (allActivities.length === 0) {
758
+ container.innerHTML = '<p class="text-muted">No activity recorded yet.</p>';
759
+ return;
760
+ }
761
+
762
+ const activitiesHTML = allActivities.slice(0, 20).map(activity => {
763
+ let iconClass, icon;
764
+ if (activity.type === 'click') {
765
+ iconClass = 'click';
766
+ icon = 'fas fa-mouse-pointer';
767
+ } else if (activity.type === 'detailed_view') {
768
+ iconClass = 'visit';
769
+ icon = 'fas fa-clock';
770
+ } else if (activity.type === 'search') {
771
+ iconClass = 'search';
772
+ icon = 'fas fa-search';
773
+ } else {
774
+ iconClass = 'visit';
775
+ icon = 'fas fa-calendar';
776
+ }
777
+
778
+ return `
779
+ <div class="activity-item">
780
+ <div class="d-flex align-items-center">
781
+ <div class="activity-icon ${iconClass} me-3">
782
+ <i class="${icon}"></i>
783
+ </div>
784
+ <div>
785
+ <div class="fw-bold">${activity.text}</div>
786
+ <small class="text-muted">${activity.timestamp.toLocaleString()}</small>
787
+ ${activity.price ? `<br><small class="text-primary">β‚Ή${this.formatPrice(activity.price)}</small>` : ''}
788
+ ${activity.viewCount ? `<br><small class="text-success">Viewed ${activity.viewCount} times</small>` : ''}
789
+ </div>
790
+ </div>
791
+ </div>
792
+ `;
793
+ }).join('');
794
+
795
+ container.innerHTML = activitiesHTML;
796
+ }
797
+
798
+ updateLastUpdated() {
799
+ const now = new Date();
800
+ document.getElementById('lastUpdated').textContent = now.toLocaleString();
801
+ }
802
+
803
+ updateLeadScore() {
804
+ // Calculate lead score based on user behavior
805
+ let score = 0;
806
+
807
+ // Points for clicks
808
+ score += this.trackingData.clickedProperties.length * 5;
809
+
810
+ // Points for detailed views
811
+ score += this.trackingData.detailedViews.length * 10;
812
+
813
+ // Points for searches
814
+ score += this.trackingData.searchHistory.length * 3;
815
+
816
+ // Points for visit requests
817
+ const visitHistory = JSON.parse(localStorage.getItem('visitHistory') || '[]');
818
+ score += visitHistory.length * 15;
819
+
820
+ // Points for time spent
821
+ const totalViewTime = this.trackingData.detailedViews.reduce((sum, view) => sum + view.totalDuration, 0);
822
+ score += Math.min(totalViewTime / 60, 20); // Max 20 points for time
823
+
824
+ // Cap score at 100
825
+ score = Math.min(score, 100);
826
+
827
+ document.getElementById('leadScore').textContent = Math.round(score);
828
+
829
+ // Update lead status
830
+ const statusElement = document.getElementById('leadStatus');
831
+ statusElement.className = 'lead-status';
832
+
833
+ if (score >= 70) {
834
+ statusElement.textContent = 'Hot Lead';
835
+ statusElement.classList.add('hot');
836
+ } else if (score >= 40) {
837
+ statusElement.textContent = 'Warm Lead';
838
+ statusElement.classList.add('warm');
839
+ } else {
840
+ statusElement.textContent = 'Cold Lead';
841
+ statusElement.classList.add('cold');
842
+ }
843
+ }
844
+
845
+ formatPrice(price) {
846
+ if (price >= 10000000) {
847
+ return (price / 10000000).toFixed(1) + 'Cr';
848
+ } else if (price >= 100000) {
849
+ return (price / 100000).toFixed(1) + 'L';
850
+ } else {
851
+ return price.toLocaleString();
852
+ }
853
+ }
854
+
855
+ formatDuration(seconds) {
856
+ if (seconds < 60) {
857
+ return seconds + 's';
858
+ } else if (seconds < 3600) {
859
+ const minutes = Math.floor(seconds / 60);
860
+ const remainingSeconds = seconds % 60;
861
+ return minutes + 'm ' + remainingSeconds + 's';
862
+ } else {
863
+ const hours = Math.floor(seconds / 3600);
864
+ const minutes = Math.floor((seconds % 3600) / 60);
865
+ return hours + 'h ' + minutes + 'm';
866
+ }
867
+ }
868
+
869
+ formatPriceRange(range) {
870
+ const ranges = {
871
+ '0-500000': 'Under β‚Ή5L',
872
+ '500000-1000000': 'β‚Ή5L - β‚Ή10L',
873
+ '1000000-2000000': 'β‚Ή10L - β‚Ή20L',
874
+ '2000000-5000000': 'β‚Ή20L - β‚Ή50L',
875
+ '5000000-10000000': 'β‚Ή50L - β‚Ή1Cr',
876
+ '10000000+': 'Above β‚Ή1Cr'
877
+ };
878
+ return ranges[range] || range;
879
+ }
880
+ }
881
+
882
+ // Global functions for buttons
883
+ function clearData() {
884
+ if (confirm('Are you sure you want to clear all tracking data? This cannot be undone.')) {
885
+ localStorage.removeItem('userTrackingData');
886
+ localStorage.removeItem('userSessionId');
887
+ localStorage.removeItem('visitHistory');
888
+ location.reload();
889
+ }
890
+ }
891
+
892
+ function exportData() {
893
+ const trackingData = localStorage.getItem('userTrackingData');
894
+ const visitHistory = localStorage.getItem('visitHistory');
895
+
896
+ const exportData = {
897
+ tracking_data: trackingData ? JSON.parse(trackingData) : {},
898
+ visit_history: visitHistory ? JSON.parse(visitHistory) : [],
899
+ export_timestamp: new Date().toISOString()
900
+ };
901
+
902
+ const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
903
+ const url = URL.createObjectURL(blob);
904
+ const a = document.createElement('a');
905
+ a.href = url;
906
+ a.download = 'lead_analytics_data.json';
907
+ document.body.appendChild(a);
908
+ a.click();
909
+ document.body.removeChild(a);
910
+ URL.revokeObjectURL(url);
911
+ }
912
+
913
+ // Initialize dashboard
914
+ document.addEventListener('DOMContentLoaded', () => {
915
+ new LeadAnalyticsDashboard();
916
+ });
917
+ </script>
918
+ </body>
919
+ </html>
templates/templates/enhanced_dashboard.html ADDED
@@ -0,0 +1,1264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Real Estate Lead Qualification Dashboard</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
9
+ <link href="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.min.css" rel="stylesheet">
10
+ <style>
11
+ :root {
12
+ --primary-color: #2c3e50;
13
+ --secondary-color: #3498db;
14
+ --success-color: #27ae60;
15
+ --warning-color: #f39c12;
16
+ --danger-color: #e74c3c;
17
+ --light-bg: #f8f9fa;
18
+ --dark-bg: #343a40;
19
+ }
20
+
21
+ body {
22
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
23
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
24
+ min-height: 100vh;
25
+ }
26
+
27
+ .dashboard-container {
28
+ background: rgba(255, 255, 255, 0.95);
29
+ border-radius: 20px;
30
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
31
+ margin: 20px;
32
+ overflow: hidden;
33
+ }
34
+
35
+ .header {
36
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
37
+ color: white;
38
+ padding: 30px;
39
+ text-align: center;
40
+ }
41
+
42
+ .header h1 {
43
+ margin: 0;
44
+ font-size: 2.5rem;
45
+ font-weight: 300;
46
+ }
47
+
48
+ .header p {
49
+ margin: 10px 0 0 0;
50
+ opacity: 0.9;
51
+ font-size: 1.1rem;
52
+ }
53
+
54
+ .search-section {
55
+ background: white;
56
+ padding: 30px;
57
+ border-bottom: 1px solid #eee;
58
+ }
59
+
60
+ .search-box {
61
+ background: var(--light-bg);
62
+ border: none;
63
+ border-radius: 50px;
64
+ padding: 15px 25px;
65
+ font-size: 1.1rem;
66
+ width: 100%;
67
+ max-width: 400px;
68
+ transition: all 0.3s ease;
69
+ }
70
+
71
+ .search-box:focus {
72
+ outline: none;
73
+ box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.3);
74
+ transform: translateY(-2px);
75
+ }
76
+
77
+ .btn-primary {
78
+ background: linear-gradient(135deg, var(--secondary-color), #2980b9);
79
+ border: none;
80
+ border-radius: 50px;
81
+ padding: 15px 30px;
82
+ font-weight: 600;
83
+ transition: all 0.3s ease;
84
+ }
85
+
86
+ .btn-primary:hover {
87
+ transform: translateY(-2px);
88
+ box-shadow: 0 10px 20px rgba(52, 152, 219, 0.3);
89
+ }
90
+
91
+ .content-section {
92
+ padding: 30px;
93
+ }
94
+
95
+ .stats-card {
96
+ background: white;
97
+ border-radius: 15px;
98
+ padding: 25px;
99
+ margin-bottom: 20px;
100
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
101
+ transition: all 0.3s ease;
102
+ border-left: 5px solid var(--secondary-color);
103
+ }
104
+
105
+ .stats-card:hover {
106
+ transform: translateY(-5px);
107
+ box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15);
108
+ }
109
+
110
+ .stats-card.success {
111
+ border-left-color: var(--success-color);
112
+ }
113
+
114
+ .stats-card.warning {
115
+ border-left-color: var(--warning-color);
116
+ }
117
+
118
+ .stats-card.danger {
119
+ border-left-color: var(--danger-color);
120
+ }
121
+
122
+ .stats-number {
123
+ font-size: 2.5rem;
124
+ font-weight: 700;
125
+ color: var(--primary-color);
126
+ margin-bottom: 10px;
127
+ }
128
+
129
+ .stats-label {
130
+ color: #666;
131
+ font-size: 0.9rem;
132
+ text-transform: uppercase;
133
+ letter-spacing: 1px;
134
+ }
135
+
136
+ .property-card {
137
+ background: white;
138
+ border-radius: 15px;
139
+ padding: 20px;
140
+ margin-bottom: 20px;
141
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
142
+ transition: all 0.3s ease;
143
+ border: 1px solid #eee;
144
+ }
145
+
146
+ .property-card:hover {
147
+ transform: translateY(-3px);
148
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
149
+ }
150
+
151
+ .property-name {
152
+ font-size: 1.2rem;
153
+ font-weight: 600;
154
+ color: var(--primary-color);
155
+ margin-bottom: 10px;
156
+ }
157
+
158
+ .property-details {
159
+ display: flex;
160
+ justify-content: space-between;
161
+ align-items: center;
162
+ flex-wrap: wrap;
163
+ gap: 10px;
164
+ }
165
+
166
+ .property-badge {
167
+ background: var(--light-bg);
168
+ color: var(--primary-color);
169
+ padding: 5px 12px;
170
+ border-radius: 20px;
171
+ font-size: 0.8rem;
172
+ font-weight: 600;
173
+ }
174
+
175
+ .price {
176
+ font-size: 1.3rem;
177
+ font-weight: 700;
178
+ color: var(--success-color);
179
+ }
180
+
181
+ .engagement-score {
182
+ display: inline-block;
183
+ padding: 5px 12px;
184
+ border-radius: 20px;
185
+ font-size: 0.8rem;
186
+ font-weight: 600;
187
+ color: white;
188
+ }
189
+
190
+ .engagement-high {
191
+ background: var(--success-color);
192
+ }
193
+
194
+ .engagement-medium {
195
+ background: var(--warning-color);
196
+ }
197
+
198
+ .engagement-low {
199
+ background: var(--danger-color);
200
+ }
201
+
202
+ .loading {
203
+ text-align: center;
204
+ padding: 50px;
205
+ color: #666;
206
+ }
207
+
208
+ .loading i {
209
+ font-size: 3rem;
210
+ color: var(--secondary-color);
211
+ animation: spin 1s linear infinite;
212
+ }
213
+
214
+ @keyframes spin {
215
+ 0% { transform: rotate(0deg); }
216
+ 100% { transform: rotate(360deg); }
217
+ }
218
+
219
+ .error-message {
220
+ background: #fee;
221
+ color: var(--danger-color);
222
+ padding: 20px;
223
+ border-radius: 10px;
224
+ border: 1px solid #fcc;
225
+ margin: 20px 0;
226
+ }
227
+
228
+ .chart-container {
229
+ background: white;
230
+ border-radius: 15px;
231
+ padding: 25px;
232
+ margin-bottom: 20px;
233
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
234
+ }
235
+
236
+ .analytics-section {
237
+ background: white;
238
+ border-radius: 15px;
239
+ padding: 25px;
240
+ margin-bottom: 20px;
241
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
242
+ }
243
+
244
+ .insight-item {
245
+ background: var(--light-bg);
246
+ padding: 15px;
247
+ border-radius: 10px;
248
+ margin-bottom: 10px;
249
+ border-left: 4px solid var(--secondary-color);
250
+ }
251
+
252
+ .insight-label {
253
+ font-weight: 600;
254
+ color: var(--primary-color);
255
+ margin-bottom: 5px;
256
+ }
257
+
258
+ .insight-value {
259
+ color: #666;
260
+ }
261
+
262
+ .recommendation {
263
+ background: #e8f5e8;
264
+ border: 1px solid #c3e6c3;
265
+ color: #2d5a2d;
266
+ padding: 15px;
267
+ border-radius: 10px;
268
+ margin-bottom: 10px;
269
+ }
270
+
271
+ .risk-high {
272
+ background: #fee;
273
+ border-color: #fcc;
274
+ color: #c33;
275
+ }
276
+
277
+ .risk-medium {
278
+ background: #fff3cd;
279
+ border-color: #ffeaa7;
280
+ color: #856404;
281
+ }
282
+
283
+ .risk-low {
284
+ background: #e8f5e8;
285
+ border-color: #c3e6c3;
286
+ color: #2d5a2d;
287
+ }
288
+
289
+ .no-data {
290
+ text-align: center;
291
+ padding: 50px;
292
+ color: #666;
293
+ }
294
+
295
+ .no-data i {
296
+ font-size: 4rem;
297
+ color: #ddd;
298
+ margin-bottom: 20px;
299
+ }
300
+
301
+ @media (max-width: 768px) {
302
+ .dashboard-container {
303
+ margin: 10px;
304
+ }
305
+
306
+ .header h1 {
307
+ font-size: 2rem;
308
+ }
309
+
310
+ .stats-number {
311
+ font-size: 2rem;
312
+ }
313
+
314
+ .property-details {
315
+ flex-direction: column;
316
+ align-items: flex-start;
317
+ }
318
+ }
319
+ </style>
320
+ </head>
321
+ <body>
322
+ <div class="dashboard-container">
323
+ <!-- Header -->
324
+ <div class="header">
325
+ <h1><i class="fas fa-chart-line"></i> Lead Qualification Dashboard</h1>
326
+ <p>Real-time property engagement analytics and insights</p>
327
+ </div>
328
+
329
+ <!-- Search Section -->
330
+ <div class="search-section">
331
+ <div class="row align-items-center">
332
+ <div class="col-md-6">
333
+ <div class="input-group">
334
+ <input type="number" id="customerIdInput" class="form-control search-box"
335
+ placeholder="Enter Customer ID (e.g., 105)" min="1">
336
+ <button class="btn btn-primary" onclick="loadCustomerData()">
337
+ <i class="fas fa-search"></i> Load Data
338
+ </button>
339
+ </div>
340
+ </div>
341
+ <div class="col-md-6 text-end">
342
+ <button class="btn btn-outline-secondary" onclick="loadSampleData()">
343
+ <i class="fas fa-eye"></i> View Sample Data
344
+ </button>
345
+ <button class="btn btn-outline-info" onclick="exportData()">
346
+ <i class="fas fa-download"></i> Export
347
+ </button>
348
+ </div>
349
+ </div>
350
+ </div>
351
+
352
+ <!-- Content Section -->
353
+ <div class="content-section">
354
+ <!-- Loading State -->
355
+ <div id="loadingState" class="loading" style="display: none;">
356
+ <i class="fas fa-spinner"></i>
357
+ <p>Loading customer data...</p>
358
+ </div>
359
+
360
+ <!-- Error State -->
361
+ <div id="errorState" class="error-message" style="display: none;">
362
+ <i class="fas fa-exclamation-triangle"></i>
363
+ <span id="errorMessage"></span>
364
+ </div>
365
+
366
+ <!-- No Data State -->
367
+ <div id="noDataState" class="no-data" style="display: none;">
368
+ <i class="fas fa-search"></i>
369
+ <h3>No Data Found</h3>
370
+ <p>Enter a customer ID to view their property engagement data</p>
371
+ </div>
372
+
373
+ <!-- Data Content -->
374
+ <div id="dataContent" style="display: none;">
375
+ <!-- Summary Statistics -->
376
+ <div class="row mb-4">
377
+ <div class="col-md-3">
378
+ <div class="stats-card">
379
+ <div class="stats-number" id="totalProperties">0</div>
380
+ <div class="stats-label">Total Properties</div>
381
+ </div>
382
+ </div>
383
+ <div class="col-md-3">
384
+ <div class="stats-card">
385
+ <div class="stats-number" id="totalViews">0</div>
386
+ <div class="stats-label">Total Views</div>
387
+ </div>
388
+ </div>
389
+ <div class="col-md-3">
390
+ <div class="stats-card">
391
+ <div class="stats-number" id="avgPrice">β‚Ή0</div>
392
+ <div class="stats-label">Average Price</div>
393
+ </div>
394
+ </div>
395
+ <div class="col-md-3">
396
+ <div class="stats-card">
397
+ <div class="stats-number" id="engagementScore">0</div>
398
+ <div class="stats-label">Engagement Score</div>
399
+ </div>
400
+ </div>
401
+ </div>
402
+
403
+ <!-- Charts Section -->
404
+ <div class="row mb-4">
405
+ <div class="col-md-6">
406
+ <div class="chart-container">
407
+ <h5><i class="fas fa-chart-pie"></i> Property Type Distribution</h5>
408
+ <canvas id="propertyTypeChart"></canvas>
409
+ </div>
410
+ </div>
411
+ <div class="col-md-6">
412
+ <div class="chart-container">
413
+ <h5><i class="fas fa-chart-bar"></i> Engagement by Property</h5>
414
+ <canvas id="engagementChart"></canvas>
415
+ </div>
416
+ </div>
417
+ </div>
418
+
419
+ <!-- Lead Qualification Section -->
420
+ <div class="row mb-4">
421
+ <div class="col-12">
422
+ <div class="analytics-section">
423
+ <h5><i class="fas fa-fire"></i> Lead Qualification Analysis</h5>
424
+ <div id="leadQualificationContainer"></div>
425
+ </div>
426
+ </div>
427
+ </div>
428
+
429
+ <!-- Detailed Analytics Section -->
430
+ <div class="row mb-4">
431
+ <div class="col-md-6">
432
+ <div class="analytics-section">
433
+ <h5><i class="fas fa-brain"></i> AI Insights</h5>
434
+ <div id="insightsContainer"></div>
435
+ </div>
436
+ </div>
437
+ <div class="col-md-6">
438
+ <div class="analytics-section">
439
+ <h5><i class="fas fa-lightbulb"></i> Recommendations</h5>
440
+ <div id="recommendationsContainer"></div>
441
+ </div>
442
+ </div>
443
+ </div>
444
+
445
+ <!-- Property Analysis Section -->
446
+ <div class="row mb-4">
447
+ <div class="col-12">
448
+ <div class="analytics-section">
449
+ <h5><i class="fas fa-home"></i> Detailed Property Analysis</h5>
450
+ <div id="propertyAnalysisContainer"></div>
451
+ </div>
452
+ </div>
453
+ </div>
454
+
455
+ <!-- Conversion Probability Section -->
456
+ <div class="row mb-4">
457
+ <div class="col-md-6">
458
+ <div class="analytics-section">
459
+ <h5><i class="fas fa-chart-line"></i> Conversion Probability</h5>
460
+ <div id="conversionContainer"></div>
461
+ </div>
462
+ </div>
463
+ <div class="col-md-6">
464
+ <div class="analytics-section">
465
+ <h5><i class="fas fa-clock"></i> Lead Timeline</h5>
466
+ <div id="timelineContainer"></div>
467
+ </div>
468
+ </div>
469
+ </div>
470
+
471
+ <!-- Properties List -->
472
+ <div class="row">
473
+ <div class="col-12">
474
+ <h5><i class="fas fa-home"></i> Property Engagement Details</h5>
475
+ <div id="propertiesContainer"></div>
476
+ </div>
477
+ </div>
478
+ </div>
479
+ </div>
480
+ </div>
481
+
482
+ <!-- Scripts -->
483
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
484
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"
485
+ onerror="console.error('Failed to load Chart.js from primary CDN')"></script>
486
+ <script>
487
+ // Fallback for Chart.js if primary CDN fails
488
+ if (typeof Chart === 'undefined') {
489
+ const script = document.createElement('script');
490
+ script.src = 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.js';
491
+ script.onload = function() {
492
+ console.log('Chart.js loaded from fallback CDN');
493
+ };
494
+ script.onerror = function() {
495
+ console.error('Failed to load Chart.js from fallback CDN');
496
+ };
497
+ document.head.appendChild(script);
498
+ }
499
+ </script>
500
+ <script>
501
+ let currentCustomerId = null;
502
+ let propertyTypeChart = null;
503
+ let engagementChart = null;
504
+
505
+ // Initialize dashboard
506
+ document.addEventListener('DOMContentLoaded', function() {
507
+ // Load sample data on page load
508
+ loadSampleData();
509
+
510
+ // Add enter key support for search
511
+ document.getElementById('customerIdInput').addEventListener('keypress', function(e) {
512
+ if (e.key === 'Enter') {
513
+ loadCustomerData();
514
+ }
515
+ });
516
+ });
517
+
518
+ function showLoading() {
519
+ document.getElementById('loadingState').style.display = 'block';
520
+ document.getElementById('errorState').style.display = 'none';
521
+ document.getElementById('noDataState').style.display = 'none';
522
+ document.getElementById('dataContent').style.display = 'none';
523
+ }
524
+
525
+ function showError(message) {
526
+ document.getElementById('loadingState').style.display = 'none';
527
+ document.getElementById('errorState').style.display = 'block';
528
+ document.getElementById('errorMessage').textContent = message;
529
+ document.getElementById('noDataState').style.display = 'none';
530
+ document.getElementById('dataContent').style.display = 'none';
531
+ }
532
+
533
+ function showNoData() {
534
+ document.getElementById('loadingState').style.display = 'none';
535
+ document.getElementById('errorState').style.display = 'none';
536
+ document.getElementById('noDataState').style.display = 'block';
537
+ document.getElementById('dataContent').style.display = 'none';
538
+ }
539
+
540
+ function showData() {
541
+ document.getElementById('loadingState').style.display = 'none';
542
+ document.getElementById('errorState').style.display = 'none';
543
+ document.getElementById('noDataState').style.display = 'none';
544
+ document.getElementById('dataContent').style.display = 'block';
545
+ }
546
+
547
+ function formatPrice(price) {
548
+ return new Intl.NumberFormat('en-IN', {
549
+ style: 'currency',
550
+ currency: 'INR',
551
+ maximumFractionDigits: 0
552
+ }).format(price);
553
+ }
554
+
555
+ function formatDuration(seconds) {
556
+ const hours = Math.floor(seconds / 3600);
557
+ const minutes = Math.floor((seconds % 3600) / 60);
558
+ return `${hours}h ${minutes}m`;
559
+ }
560
+
561
+ function getEngagementClass(score) {
562
+ if (score >= 80) return 'engagement-high';
563
+ if (score >= 50) return 'engagement-medium';
564
+ return 'engagement-low';
565
+ }
566
+
567
+ function getRiskClass(level) {
568
+ switch(level.toLowerCase()) {
569
+ case 'high': return 'risk-high';
570
+ case 'medium': return 'risk-medium';
571
+ case 'low': return 'risk-low';
572
+ default: return '';
573
+ }
574
+ }
575
+
576
+ async function loadCustomerData() {
577
+ const customerId = document.getElementById('customerIdInput').value.trim();
578
+
579
+ if (!customerId) {
580
+ showError('Please enter a customer ID');
581
+ return;
582
+ }
583
+
584
+ showLoading();
585
+ currentCustomerId = customerId;
586
+
587
+ try {
588
+ console.log('πŸ” Making combined API request for customer:', customerId);
589
+ console.log('πŸ“‘ Request URL:', `/api/lead-analysis/${customerId}`);
590
+
591
+ // Load combined lead analysis data
592
+ const response = await fetch(`/api/lead-analysis/${customerId}`);
593
+ console.log('πŸ“₯ Response status:', response.status);
594
+ console.log('πŸ“₯ Response headers:', Object.fromEntries(response.headers.entries()));
595
+
596
+ if (!response.ok) {
597
+ throw new Error(`HTTP error! status: ${response.status}`);
598
+ }
599
+
600
+ const combinedData = await response.json();
601
+ console.log('πŸ“Š Combined Lead Analysis Data:', combinedData);
602
+
603
+ if (combinedData.error) {
604
+ showError(combinedData.error);
605
+ return;
606
+ }
607
+
608
+ if (!combinedData.properties || combinedData.properties.length === 0) {
609
+ showNoData();
610
+ return;
611
+ }
612
+
613
+ // Display network info
614
+ displayNetworkInfo(customerId, combinedData);
615
+
616
+ // Display all data from single response
617
+ displayData(combinedData);
618
+
619
+ } catch (error) {
620
+ console.error('❌ Error loading data:', error);
621
+ showError(`Failed to load data: ${error.message}`);
622
+ }
623
+ }
624
+
625
+ function loadSampleData() {
626
+ document.getElementById('customerIdInput').value = '105';
627
+ loadCustomerData();
628
+ }
629
+
630
+ function displayData(data) {
631
+ showData();
632
+
633
+ // Update summary statistics
634
+ const summary = data.summary;
635
+ document.getElementById('totalProperties').textContent = summary.total_properties;
636
+ document.getElementById('totalViews').textContent = summary.total_views;
637
+ document.getElementById('avgPrice').textContent = formatPrice(summary.average_price);
638
+ document.getElementById('engagementScore').textContent = Math.round(summary.engagement_score);
639
+
640
+ // Display all sections immediately
641
+ displayLeadQualification(data);
642
+ displayInsights(data);
643
+ displayRecommendations(data);
644
+ displayPropertyAnalysis(data);
645
+ displayConversionProbability(data);
646
+ displayTimeline(data);
647
+ displayProperties(data.properties);
648
+
649
+ // Create charts with delay to ensure Chart.js is loaded
650
+ setTimeout(() => {
651
+ createPropertyTypeChart(summary.property_types);
652
+ createEngagementChart(data.properties);
653
+ }, 500);
654
+ }
655
+
656
+ function displayLeadQualification(data) {
657
+ const container = document.getElementById('leadQualificationContainer');
658
+ container.innerHTML = '';
659
+
660
+ if (!data || !data.lead_qualification) {
661
+ container.innerHTML = '<p class="text-muted">No lead qualification data available</p>';
662
+ return;
663
+ }
664
+
665
+ const lead = data.lead_qualification;
666
+
667
+ // Lead status card
668
+ const statusCard = document.createElement('div');
669
+ statusCard.className = 'row mb-3';
670
+ statusCard.innerHTML = `
671
+ <div class="col-md-6">
672
+ <div class="stats-card" style="border-left-color: ${lead.status_color}">
673
+ <div class="stats-number" style="color: ${lead.status_color}">${lead.lead_status}</div>
674
+ <div class="stats-label">Lead Status</div>
675
+ <small class="text-muted">${lead.status_description}</small>
676
+ </div>
677
+ </div>
678
+ <div class="col-md-6">
679
+ <div class="stats-card">
680
+ <div class="stats-number">${lead.lead_score}/100</div>
681
+ <div class="stats-label">Lead Score</div>
682
+ <small class="text-muted">Based on multiple factors</small>
683
+ </div>
684
+ </div>
685
+ `;
686
+ container.appendChild(statusCard);
687
+
688
+ // Factors breakdown
689
+ const factorsCard = document.createElement('div');
690
+ factorsCard.className = 'row';
691
+ factorsCard.innerHTML = '<div class="col-12"><h6>Lead Score Breakdown:</h6></div>';
692
+
693
+ Object.entries(lead.factors).forEach(([factor, data]) => {
694
+ const factorDiv = document.createElement('div');
695
+ factorDiv.className = 'col-md-6 mb-2';
696
+ factorDiv.innerHTML = `
697
+ <div class="insight-item">
698
+ <div class="d-flex justify-content-between align-items-center">
699
+ <div>
700
+ <div class="insight-label">${factor.replace('_', ' ').toUpperCase()}</div>
701
+ <div class="insight-value">${data.description}</div>
702
+ </div>
703
+ <div class="text-end">
704
+ <div class="fw-bold">${data.points.toFixed(1)}/${data.max_points}</div>
705
+ <small class="text-muted">Score: ${data.score}</small>
706
+ </div>
707
+ </div>
708
+ </div>
709
+ `;
710
+ factorsCard.appendChild(factorDiv);
711
+ });
712
+
713
+ container.appendChild(factorsCard);
714
+ }
715
+
716
+ function displayPropertyAnalysis(data) {
717
+ const container = document.getElementById('propertyAnalysisContainer');
718
+ container.innerHTML = '';
719
+
720
+ if (!data || !data.analytics || !data.analytics.property_analysis) {
721
+ container.innerHTML = '<p class="text-muted">No property analysis available</p>';
722
+ return;
723
+ }
724
+
725
+ const analysis = data.analytics.property_analysis;
726
+
727
+ // Price Analysis
728
+ if (analysis.price_analysis && analysis.price_analysis.min_price !== undefined) {
729
+ const priceDiv = document.createElement('div');
730
+ priceDiv.className = 'mb-3';
731
+ priceDiv.innerHTML = `
732
+ <h6><i class="fas fa-money-bill-wave"></i> Price Analysis</h6>
733
+ <div class="row">
734
+ <div class="col-md-3">
735
+ <div class="insight-item">
736
+ <div class="insight-label">Price Range</div>
737
+ <div class="insight-value">${formatPrice(analysis.price_analysis.min_price)} - ${formatPrice(analysis.price_analysis.max_price)}</div>
738
+ </div>
739
+ </div>
740
+ <div class="col-md-3">
741
+ <div class="insight-item">
742
+ <div class="insight-label">Average Price</div>
743
+ <div class="insight-value">${formatPrice(analysis.price_analysis.avg_price)}</div>
744
+ </div>
745
+ </div>
746
+ <div class="col-md-3">
747
+ <div class="insight-item">
748
+ <div class="insight-label">Preferred Range</div>
749
+ <div class="insight-value">${getPreferredRangeDisplay(analysis.price_analysis.preferred_range)}</div>
750
+ </div>
751
+ </div>
752
+ <div class="col-md-3">
753
+ <div class="insight-item">
754
+ <div class="insight-label">Price Trend</div>
755
+ <div class="insight-value">${analysis.price_analysis.price_trend || 'N/A'}</div>
756
+ </div>
757
+ </div>
758
+ </div>
759
+ `;
760
+ container.appendChild(priceDiv);
761
+ }
762
+
763
+ // Duration Analysis
764
+ if (analysis.duration_analysis && analysis.duration_analysis.total_duration !== undefined) {
765
+ const durationDiv = document.createElement('div');
766
+ durationDiv.className = 'mb-3';
767
+ durationDiv.innerHTML = `
768
+ <h6><i class="fas fa-clock"></i> Duration Analysis</h6>
769
+ <div class="row">
770
+ <div class="col-md-3">
771
+ <div class="insight-item">
772
+ <div class="insight-label">Total Duration</div>
773
+ <div class="insight-value">${formatDuration(analysis.duration_analysis.total_duration)}</div>
774
+ </div>
775
+ </div>
776
+ <div class="col-md-3">
777
+ <div class="insight-item">
778
+ <div class="insight-label">Average Duration</div>
779
+ <div class="insight-value">${formatDuration(analysis.duration_analysis.avg_duration)}</div>
780
+ </div>
781
+ </div>
782
+ <div class="col-md-3">
783
+ <div class="insight-item">
784
+ <div class="insight-label">Max Duration</div>
785
+ <div class="insight-value">${formatDuration(analysis.duration_analysis.max_duration)}</div>
786
+ </div>
787
+ </div>
788
+ <div class="col-md-3">
789
+ <div class="insight-item">
790
+ <div class="insight-label">Min Duration</div>
791
+ <div class="insight-value">${formatDuration(analysis.duration_analysis.min_duration)}</div>
792
+ </div>
793
+ </div>
794
+ </div>
795
+ `;
796
+ container.appendChild(durationDiv);
797
+ }
798
+
799
+ // Individual Property Analysis
800
+ if (analysis.properties && Array.isArray(analysis.properties) && analysis.properties.length > 0) {
801
+ const propertiesDiv = document.createElement('div');
802
+ propertiesDiv.className = 'mt-4';
803
+ propertiesDiv.innerHTML = '<h6><i class="fas fa-list"></i> Individual Property Analysis</h6>';
804
+
805
+ analysis.properties.forEach(prop => {
806
+ if (!prop || typeof prop !== 'object') return;
807
+
808
+ const propDiv = document.createElement('div');
809
+ propDiv.className = 'property-card mb-3';
810
+ propDiv.innerHTML = `
811
+ <div class="row">
812
+ <div class="col-md-8">
813
+ <div class="property-name">${prop.property_name || 'Unknown Property'}</div>
814
+ <div class="property-details">
815
+ <span class="property-badge">${prop.property_type || 'Unknown'}</span>
816
+ <span class="property-badge">${prop.view_count || 0} views</span>
817
+ <span class="property-badge">${prop.duration_formatted || '0h 0m'}</span>
818
+ <span class="property-badge ${getInterestClass(prop.interest_level || 'Unknown')}">${prop.interest_level || 'Unknown'} Interest</span>
819
+ </div>
820
+ </div>
821
+ <div class="col-md-4 text-end">
822
+ <div class="price">${prop.price_formatted || 'β‚Ή0'}</div>
823
+ <div class="engagement-score ${getEngagementClass(prop.engagement_score || 0)}">${Math.round(prop.engagement_score || 0)}%</div>
824
+ </div>
825
+ </div>
826
+ <div class="mt-2">
827
+ <small class="text-muted">
828
+ <i class="fas fa-lightbulb"></i> ${prop.recommendation || 'No recommendation available'}
829
+ </small>
830
+ </div>
831
+ `;
832
+ propertiesDiv.appendChild(propDiv);
833
+ });
834
+
835
+ container.appendChild(propertiesDiv);
836
+ }
837
+ }
838
+
839
+ function displayConversionProbability(data) {
840
+ const container = document.getElementById('conversionContainer');
841
+ container.innerHTML = '';
842
+
843
+ if (!data || !data.analytics || !data.analytics.conversion_probability) {
844
+ container.innerHTML = '<p class="text-muted">No conversion data available</p>';
845
+ return;
846
+ }
847
+
848
+ const conversion = data.analytics.conversion_probability;
849
+
850
+ // Main probability card
851
+ const probCard = document.createElement('div');
852
+ probCard.className = 'stats-card mb-3';
853
+ probCard.innerHTML = `
854
+ <div class="stats-number">${Math.round(conversion.final_probability)}%</div>
855
+ <div class="stats-label">Conversion Probability</div>
856
+ <small class="text-muted">Confidence: ${conversion.confidence_level}</small>
857
+ `;
858
+ container.appendChild(probCard);
859
+
860
+ // Breakdown
861
+ const breakdownDiv = document.createElement('div');
862
+ breakdownDiv.innerHTML = `
863
+ <h6>Probability Breakdown:</h6>
864
+ <div class="insight-item">
865
+ <div class="d-flex justify-content-between">
866
+ <span>Base Probability:</span>
867
+ <span>${Math.round(conversion.base_probability)}%</span>
868
+ </div>
869
+ </div>
870
+ `;
871
+
872
+ Object.entries(conversion.adjustments).forEach(([factor, value]) => {
873
+ const factorDiv = document.createElement('div');
874
+ factorDiv.className = 'insight-item';
875
+ factorDiv.innerHTML = `
876
+ <div class="d-flex justify-content-between">
877
+ <span>${factor.replace('_', ' ').toUpperCase()}:</span>
878
+ <span>+${Math.round(value)}%</span>
879
+ </div>
880
+ `;
881
+ breakdownDiv.appendChild(factorDiv);
882
+ });
883
+
884
+ container.appendChild(breakdownDiv);
885
+ }
886
+
887
+ function displayTimeline(data) {
888
+ const container = document.getElementById('timelineContainer');
889
+ container.innerHTML = '';
890
+
891
+ if (!data || !data.analytics || !data.analytics.lead_timeline || data.analytics.lead_timeline.length === 0) {
892
+ container.innerHTML = '<p class="text-muted">No timeline data available</p>';
893
+ return;
894
+ }
895
+
896
+ const timeline = data.analytics.lead_timeline;
897
+
898
+ timeline.forEach((event, index) => {
899
+ const eventDiv = document.createElement('div');
900
+ eventDiv.className = 'insight-item mb-2';
901
+ eventDiv.innerHTML = `
902
+ <div class="d-flex justify-content-between align-items-start">
903
+ <div>
904
+ <div class="insight-label">${event.property_name}</div>
905
+ <div class="insight-value">
906
+ ${event.property_type} β€’ ${formatPrice(event.price)} β€’ ${event.views} views
907
+ </div>
908
+ <small class="text-muted">${formatDate(event.date)}</small>
909
+ </div>
910
+ <div class="text-end">
911
+ <div class="engagement-score ${getEngagementClass(event.engagement_score)}">${Math.round(event.engagement_score)}%</div>
912
+ </div>
913
+ </div>
914
+ `;
915
+ container.appendChild(eventDiv);
916
+ });
917
+ }
918
+
919
+ function createPropertyTypeChart(propertyTypes) {
920
+ try {
921
+ const canvas = document.getElementById('propertyTypeChart');
922
+ if (!canvas) {
923
+ console.error('Property type chart canvas not found');
924
+ return;
925
+ }
926
+
927
+ const ctx = canvas.getContext('2d');
928
+
929
+ if (propertyTypeChart) {
930
+ propertyTypeChart.destroy();
931
+ }
932
+
933
+ const labels = Object.keys(propertyTypes);
934
+ const data = Object.values(propertyTypes);
935
+
936
+ if (typeof Chart === 'undefined') {
937
+ console.error('Chart.js not loaded');
938
+ return;
939
+ }
940
+
941
+ propertyTypeChart = new Chart(ctx, {
942
+ type: 'doughnut',
943
+ data: {
944
+ labels: labels,
945
+ datasets: [{
946
+ data: data,
947
+ backgroundColor: [
948
+ '#3498db',
949
+ '#e74c3c',
950
+ '#2ecc71',
951
+ '#f39c12',
952
+ '#9b59b6',
953
+ '#1abc9c'
954
+ ]
955
+ }]
956
+ },
957
+ options: {
958
+ responsive: true,
959
+ plugins: {
960
+ legend: {
961
+ position: 'bottom'
962
+ }
963
+ }
964
+ }
965
+ });
966
+ } catch (error) {
967
+ console.error('Error creating property type chart:', error);
968
+ }
969
+ }
970
+
971
+ function createEngagementChart(properties) {
972
+ try {
973
+ const canvas = document.getElementById('engagementChart');
974
+ if (!canvas) {
975
+ console.error('Engagement chart canvas not found');
976
+ return;
977
+ }
978
+
979
+ const ctx = canvas.getContext('2d');
980
+
981
+ if (engagementChart) {
982
+ engagementChart.destroy();
983
+ }
984
+
985
+ const labels = properties.map(p => p.propertyName.substring(0, 20) + '...');
986
+ const data = properties.map(p => p.engagement_score || 0);
987
+
988
+ if (typeof Chart === 'undefined') {
989
+ console.error('Chart.js not loaded');
990
+ return;
991
+ }
992
+
993
+ engagementChart = new Chart(ctx, {
994
+ type: 'bar',
995
+ data: {
996
+ labels: labels,
997
+ datasets: [{
998
+ label: 'Engagement Score',
999
+ data: data,
1000
+ backgroundColor: '#3498db',
1001
+ borderColor: '#2980b9',
1002
+ borderWidth: 1
1003
+ }]
1004
+ },
1005
+ options: {
1006
+ responsive: true,
1007
+ scales: {
1008
+ y: {
1009
+ beginAtZero: true,
1010
+ max: 100
1011
+ }
1012
+ },
1013
+ plugins: {
1014
+ legend: {
1015
+ display: false
1016
+ }
1017
+ }
1018
+ }
1019
+ });
1020
+ } catch (error) {
1021
+ console.error('Error creating engagement chart:', error);
1022
+ }
1023
+ }
1024
+
1025
+ function displayInsights(data) {
1026
+ const container = document.getElementById('insightsContainer');
1027
+ container.innerHTML = '';
1028
+
1029
+ if (!data || !data.analytics || Object.keys(data.analytics).length === 0) {
1030
+ container.innerHTML = '<p class="text-muted">No insights available</p>';
1031
+ return;
1032
+ }
1033
+
1034
+ const analytics = data.analytics;
1035
+ const insights = [
1036
+ { label: 'Engagement Level', value: analytics.engagement_level || 'Unknown' },
1037
+ { label: 'Preferred Types', value: (analytics.preferred_property_types || []).join(', ') || 'None' },
1038
+ { label: 'Opportunity Score', value: `${Math.round(analytics.opportunity_score || 0)}%` },
1039
+ { label: 'Risk Level', value: analytics.risk_assessment?.risk_level || 'Unknown' }
1040
+ ];
1041
+
1042
+ insights.forEach(insight => {
1043
+ const div = document.createElement('div');
1044
+ div.className = 'insight-item';
1045
+ div.innerHTML = `
1046
+ <div class="insight-label">${insight.label}</div>
1047
+ <div class="insight-value">${insight.value}</div>
1048
+ `;
1049
+ container.appendChild(div);
1050
+ });
1051
+ }
1052
+
1053
+ function displayRecommendations(data) {
1054
+ const container = document.getElementById('recommendationsContainer');
1055
+ container.innerHTML = '';
1056
+
1057
+ if (!data || !data.analytics || !data.analytics.recommendations || data.analytics.recommendations.length === 0) {
1058
+ container.innerHTML = '<p class="text-muted">No recommendations available</p>';
1059
+ return;
1060
+ }
1061
+
1062
+ data.analytics.recommendations.forEach(rec => {
1063
+ const div = document.createElement('div');
1064
+ div.className = 'recommendation';
1065
+ div.innerHTML = `<i class="fas fa-lightbulb"></i> ${rec}`;
1066
+ container.appendChild(div);
1067
+ });
1068
+
1069
+ // Add risk assessment if available
1070
+ if (data.analytics.risk_assessment && data.analytics.risk_assessment.risk_factors) {
1071
+ const riskDiv = document.createElement('div');
1072
+ riskDiv.className = `recommendation ${getRiskClass(data.analytics.risk_assessment.risk_level)}`;
1073
+ riskDiv.innerHTML = `
1074
+ <strong><i class="fas fa-exclamation-triangle"></i> Risk Factors:</strong><br>
1075
+ ${data.analytics.risk_assessment.risk_factors.join(', ')}
1076
+ `;
1077
+ container.appendChild(riskDiv);
1078
+ }
1079
+ }
1080
+
1081
+ function displayProperties(properties) {
1082
+ const container = document.getElementById('propertiesContainer');
1083
+ container.innerHTML = '';
1084
+
1085
+ properties.forEach(property => {
1086
+ const div = document.createElement('div');
1087
+ div.className = 'property-card';
1088
+
1089
+ const engagementClass = getEngagementClass(property.engagement_score || 0);
1090
+ const engagementText = property.engagement_score ? `${Math.round(property.engagement_score)}%` : 'N/A';
1091
+
1092
+ div.innerHTML = `
1093
+ <div class="property-name">${property.propertyName}</div>
1094
+ <div class="property-details">
1095
+ <div>
1096
+ <span class="property-badge">${property.propertyTypeName}</span>
1097
+ <span class="property-badge">${property.viewCount} views</span>
1098
+ <span class="property-badge">${formatDuration(property.totalDuration)}</span>
1099
+ </div>
1100
+ <div>
1101
+ <span class="price">${formatPrice(property.price)}</span>
1102
+ <span class="engagement-score ${engagementClass}">${engagementText}</span>
1103
+ </div>
1104
+ </div>
1105
+ <div class="mt-2 text-muted">
1106
+ <small>
1107
+ <i class="fas fa-clock"></i> Last viewed: ${formatDate(property.lastViewedAt)}
1108
+ ${property.last_viewed_days_ago !== undefined ? `(${property.last_viewed_days_ago} days ago)` : ''}
1109
+ </small>
1110
+ </div>
1111
+ `;
1112
+
1113
+ container.appendChild(div);
1114
+ });
1115
+ }
1116
+
1117
+ function formatDate(dateString) {
1118
+ if (!dateString) return 'Unknown';
1119
+
1120
+ try {
1121
+ const date = new Date(dateString);
1122
+ return date.toLocaleDateString('en-IN', {
1123
+ year: 'numeric',
1124
+ month: 'short',
1125
+ day: 'numeric',
1126
+ hour: '2-digit',
1127
+ minute: '2-digit'
1128
+ });
1129
+ } catch (e) {
1130
+ return 'Invalid date';
1131
+ }
1132
+ }
1133
+
1134
+ async function exportData() {
1135
+ if (!currentCustomerId) {
1136
+ alert('Please load customer data first');
1137
+ return;
1138
+ }
1139
+
1140
+ try {
1141
+ const response = await fetch(`/api/export/${currentCustomerId}?format=json`);
1142
+ const data = await response.json();
1143
+
1144
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
1145
+ const url = window.URL.createObjectURL(blob);
1146
+ const a = document.createElement('a');
1147
+ a.href = url;
1148
+ a.download = `customer_${currentCustomerId}_data.json`;
1149
+ document.body.appendChild(a);
1150
+ a.click();
1151
+ document.body.removeChild(a);
1152
+ window.URL.revokeObjectURL(url);
1153
+
1154
+ } catch (error) {
1155
+ console.error('Export error:', error);
1156
+ alert('Failed to export data');
1157
+ }
1158
+ }
1159
+
1160
+ function getInterestClass(level) {
1161
+ switch(level.toLowerCase()) {
1162
+ case 'very high': return 'engagement-high';
1163
+ case 'high': return 'engagement-medium';
1164
+ case 'medium': return 'engagement-medium';
1165
+ case 'low': return 'engagement-low';
1166
+ case 'very low': return 'engagement-low';
1167
+ default: return '';
1168
+ }
1169
+ }
1170
+
1171
+ function getPreferredRangeDisplay(preferredRange) {
1172
+ if (!preferredRange || typeof preferredRange !== 'string') {
1173
+ return 'N/A';
1174
+ }
1175
+ return preferredRange.replace('_', ' ').toUpperCase();
1176
+ }
1177
+
1178
+ function displayNetworkInfo(customerId, data) {
1179
+ // Create network info section
1180
+ const networkInfo = `
1181
+ <div class="analytics-section mb-4">
1182
+ <h5><i class="fas fa-network-wired"></i> Network Request Information</h5>
1183
+ <div class="row">
1184
+ <div class="col-md-6">
1185
+ <h6>API Requests Made:</h6>
1186
+ <div class="insight-item">
1187
+ <div class="insight-label">Lead Analysis Request</div>
1188
+ <div class="insight-value">GET /api/lead-analysis/${customerId}</div>
1189
+ <small class="text-muted">Status: 200 OK</small>
1190
+ </div>
1191
+ </div>
1192
+ <div class="col-md-6">
1193
+ <h6>Data Summary:</h6>
1194
+ <div class="insight-item">
1195
+ <div class="insight-label">Properties Found</div>
1196
+ <div class="insight-value">${data.properties ? data.properties.length : 0}</div>
1197
+ </div>
1198
+ <div class="insight-item">
1199
+ <div class="insight-label">Analytics Generated</div>
1200
+ <div class="insight-value">${Object.keys(data).length} sections</div>
1201
+ </div>
1202
+ </div>
1203
+ </div>
1204
+
1205
+ <div class="mt-3">
1206
+ <button class="btn btn-sm btn-outline-info" onclick="showRawData()">
1207
+ <i class="fas fa-code"></i> View Raw JSON Data
1208
+ </button>
1209
+ <button class="btn btn-sm btn-outline-secondary" onclick="copyToClipboard()">
1210
+ <i class="fas fa-copy"></i> Copy to Clipboard
1211
+ </button>
1212
+ </div>
1213
+
1214
+ <div id="rawDataContainer" style="display: none;" class="mt-3">
1215
+ <h6>Raw API Response Data:</h6>
1216
+ <div class="row">
1217
+ <div class="col-md-6">
1218
+ <h6>Combined Lead Analysis Data:</h6>
1219
+ <pre class="bg-light p-2 rounded" style="max-height: 300px; overflow-y: auto; font-size: 0.8rem;">${JSON.stringify(data, null, 2)}</pre>
1220
+ </div>
1221
+ </div>
1222
+ </div>
1223
+ </div>
1224
+ `;
1225
+
1226
+ // Insert network info at the top of the data content
1227
+ const dataContent = document.getElementById('dataContent');
1228
+ dataContent.insertAdjacentHTML('afterbegin', networkInfo);
1229
+ }
1230
+
1231
+ function showRawData() {
1232
+ const container = document.getElementById('rawDataContainer');
1233
+ if (container.style.display === 'none') {
1234
+ container.style.display = 'block';
1235
+ } else {
1236
+ container.style.display = 'none';
1237
+ }
1238
+ }
1239
+
1240
+ function copyToClipboard() {
1241
+ const customerId = document.getElementById('customerIdInput').value.trim();
1242
+ const data = {
1243
+ customer_id: customerId,
1244
+ timestamp: new Date().toISOString(),
1245
+ api_requests: [
1246
+ {
1247
+ url: `/api/lead-analysis/${customerId}`,
1248
+ method: 'GET',
1249
+ description: 'Combined lead analysis data'
1250
+ }
1251
+ ],
1252
+ note: 'Check browser Network tab for actual request/response details'
1253
+ };
1254
+
1255
+ navigator.clipboard.writeText(JSON.stringify(data, null, 2)).then(() => {
1256
+ alert('Network information copied to clipboard!');
1257
+ }).catch(() => {
1258
+ alert('Failed to copy to clipboard. Check console for data.');
1259
+ console.log('Network Info:', data);
1260
+ });
1261
+ }
1262
+ </script>
1263
+ </body>
1264
+ </html>
templates/templates/index.html ADDED
@@ -0,0 +1,2272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Real Estate Lead Qualification Engine</title>
7
+
8
+ <!-- Bootstrap CSS -->
9
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
10
+
11
+ <!-- Font Awesome -->
12
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
13
+
14
+ <!-- Google Fonts -->
15
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
16
+
17
+ <!-- Custom CSS -->
18
+ <style>
19
+ :root {
20
+ --primary-color: #2563eb;
21
+ --secondary-color: #64748b;
22
+ --success-color: #10b981;
23
+ --warning-color: #f59e0b;
24
+ --danger-color: #ef4444;
25
+ --dark-color: #1e293b;
26
+ --light-color: #f8fafc;
27
+ --border-color: #e2e8f0;
28
+ }
29
+
30
+ * {
31
+ margin: 0;
32
+ padding: 0;
33
+ box-sizing: border-box;
34
+ }
35
+
36
+ body {
37
+ font-family: 'Inter', sans-serif;
38
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
39
+ min-height: 100vh;
40
+ color: var(--dark-color);
41
+ }
42
+
43
+ .navbar {
44
+ background: rgba(255, 255, 255, 0.95) !important;
45
+ backdrop-filter: blur(10px);
46
+ border-bottom: 1px solid var(--border-color);
47
+ }
48
+
49
+ .hero-section {
50
+ background: linear-gradient(135deg, rgba(37, 99, 235, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
51
+ padding: 80px 0;
52
+ text-align: center;
53
+ }
54
+
55
+ .hero-title {
56
+ font-size: 3.5rem;
57
+ font-weight: 700;
58
+ color: var(--dark-color);
59
+ margin-bottom: 1rem;
60
+ }
61
+
62
+ .hero-subtitle {
63
+ font-size: 1.25rem;
64
+ color: var(--secondary-color);
65
+ margin-bottom: 2rem;
66
+ }
67
+
68
+ .search-container {
69
+ background: white;
70
+ border-radius: 20px;
71
+ padding: 2rem;
72
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
73
+ margin-bottom: 3rem;
74
+ }
75
+
76
+ .property-grid {
77
+ display: grid;
78
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
79
+ gap: 2rem;
80
+ margin-top: 2rem;
81
+ }
82
+
83
+ .property-card {
84
+ background: white;
85
+ border-radius: 15px;
86
+ overflow: hidden;
87
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
88
+ transition: all 0.3s ease;
89
+ cursor: pointer;
90
+ position: relative;
91
+ }
92
+
93
+ .property-card:hover {
94
+ transform: translateY(-5px);
95
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
96
+ }
97
+
98
+ .property-image {
99
+ width: 100%;
100
+ height: 250px;
101
+ object-fit: cover;
102
+ transition: transform 0.3s ease;
103
+ }
104
+
105
+ .property-card:hover .property-image {
106
+ transform: scale(1.05);
107
+ }
108
+
109
+ .property-badge {
110
+ position: absolute;
111
+ top: 15px;
112
+ right: 15px;
113
+ background: var(--primary-color);
114
+ color: white;
115
+ padding: 0.5rem 1rem;
116
+ border-radius: 20px;
117
+ font-size: 0.875rem;
118
+ font-weight: 500;
119
+ }
120
+
121
+ .property-content {
122
+ padding: 1.5rem;
123
+ }
124
+
125
+ .property-title {
126
+ font-size: 1.25rem;
127
+ font-weight: 600;
128
+ margin-bottom: 0.5rem;
129
+ color: var(--dark-color);
130
+ }
131
+
132
+ .property-price {
133
+ font-size: 1.5rem;
134
+ font-weight: 700;
135
+ color: var(--primary-color);
136
+ margin-bottom: 1rem;
137
+ }
138
+
139
+ .property-location {
140
+ color: var(--secondary-color);
141
+ margin-bottom: 1rem;
142
+ font-size: 0.875rem;
143
+ }
144
+
145
+ .property-features {
146
+ display: flex;
147
+ flex-wrap: wrap;
148
+ gap: 0.5rem;
149
+ margin-bottom: 1rem;
150
+ }
151
+
152
+ .feature-tag {
153
+ background: var(--light-color);
154
+ color: var(--secondary-color);
155
+ padding: 0.25rem 0.75rem;
156
+ border-radius: 15px;
157
+ font-size: 0.75rem;
158
+ font-weight: 500;
159
+ }
160
+
161
+ .feature-tag.available {
162
+ background: #dcfce7;
163
+ color: #166534;
164
+ }
165
+
166
+ .property-actions {
167
+ display: flex;
168
+ gap: 0.5rem;
169
+ margin-top: 1rem;
170
+ }
171
+
172
+ .btn-action {
173
+ flex: 1;
174
+ padding: 0.75rem;
175
+ border: none;
176
+ border-radius: 10px;
177
+ font-weight: 500;
178
+ transition: all 0.3s ease;
179
+ text-decoration: none;
180
+ text-align: center;
181
+ }
182
+
183
+ .btn-primary-custom {
184
+ background: var(--primary-color);
185
+ color: white;
186
+ }
187
+
188
+ .btn-primary-custom:hover {
189
+ background: #1d4ed8;
190
+ color: white;
191
+ }
192
+
193
+ .btn-outline-custom {
194
+ background: transparent;
195
+ color: var(--primary-color);
196
+ border: 2px solid var(--primary-color);
197
+ }
198
+
199
+ .btn-outline-custom:hover {
200
+ background: var(--primary-color);
201
+ color: white;
202
+ }
203
+
204
+ .floating-email {
205
+ position: fixed;
206
+ bottom: 30px;
207
+ right: 30px;
208
+ background: white;
209
+ border-radius: 15px;
210
+ padding: 1.5rem;
211
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
212
+ z-index: 1000;
213
+ max-width: 350px;
214
+ display: none;
215
+ border: 2px solid var(--primary-color);
216
+ }
217
+
218
+ .floating-email.show {
219
+ display: block;
220
+ animation: slideIn 0.3s ease, pulse 2s infinite;
221
+ }
222
+
223
+ .floating-email.hidden {
224
+ display: none !important;
225
+ }
226
+
227
+ @keyframes pulse {
228
+ 0% {
229
+ box-shadow: 0 10px 30px rgba(37, 99, 235, 0.2);
230
+ }
231
+ 50% {
232
+ box-shadow: 0 10px 30px rgba(37, 99, 235, 0.4);
233
+ }
234
+ 100% {
235
+ box-shadow: 0 10px 30px rgba(37, 99, 235, 0.2);
236
+ }
237
+ }
238
+
239
+ @keyframes slideIn {
240
+ from {
241
+ transform: translateX(100%);
242
+ opacity: 0;
243
+ }
244
+ to {
245
+ transform: translateX(0);
246
+ opacity: 1;
247
+ }
248
+ }
249
+
250
+ .email-header {
251
+ display: flex;
252
+ align-items: center;
253
+ gap: 0.5rem;
254
+ margin-bottom: 1rem;
255
+ }
256
+
257
+ .email-icon {
258
+ width: 40px;
259
+ height: 40px;
260
+ background: var(--primary-color);
261
+ border-radius: 50%;
262
+ display: flex;
263
+ align-items: center;
264
+ justify-content: center;
265
+ color: white;
266
+ }
267
+
268
+ .loading-spinner {
269
+ display: none;
270
+ text-align: center;
271
+ padding: 2rem;
272
+ }
273
+
274
+ .spinner {
275
+ width: 40px;
276
+ height: 40px;
277
+ border: 4px solid var(--border-color);
278
+ border-top: 4px solid var(--primary-color);
279
+ border-radius: 50%;
280
+ animation: spin 1s linear infinite;
281
+ }
282
+
283
+ @keyframes spin {
284
+ 0% { transform: rotate(0deg); }
285
+ 100% { transform: rotate(360deg); }
286
+ }
287
+
288
+ .recommendations-section {
289
+ background: white;
290
+ border-radius: 20px;
291
+ padding: 2rem;
292
+ margin-top: 3rem;
293
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
294
+ }
295
+
296
+ .section-title {
297
+ font-size: 1.5rem;
298
+ font-weight: 600;
299
+ margin-bottom: 1rem;
300
+ color: var(--dark-color);
301
+ }
302
+
303
+ .stats-grid {
304
+ display: grid;
305
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
306
+ gap: 1rem;
307
+ margin-bottom: 2rem;
308
+ }
309
+
310
+ .stat-card {
311
+ background: var(--light-color);
312
+ padding: 1.5rem;
313
+ border-radius: 15px;
314
+ text-align: center;
315
+ }
316
+
317
+ .stat-number {
318
+ font-size: 2rem;
319
+ font-weight: 700;
320
+ color: var(--primary-color);
321
+ }
322
+
323
+ .stat-label {
324
+ color: var(--secondary-color);
325
+ font-size: 0.875rem;
326
+ margin-top: 0.5rem;
327
+ }
328
+
329
+ .whatsapp-float {
330
+ position: fixed;
331
+ bottom: 30px;
332
+ left: 30px;
333
+ background: #25d366;
334
+ color: white;
335
+ width: 60px;
336
+ height: 60px;
337
+ border-radius: 50%;
338
+ display: flex;
339
+ align-items: center;
340
+ justify-content: center;
341
+ font-size: 1.5rem;
342
+ box-shadow: 0 5px 15px rgba(37, 214, 102, 0.3);
343
+ transition: all 0.3s ease;
344
+ z-index: 1000;
345
+ }
346
+
347
+ .whatsapp-float:hover {
348
+ transform: scale(1.1);
349
+ color: white;
350
+ }
351
+
352
+ .visit-history-float {
353
+ position: fixed;
354
+ bottom: 100px;
355
+ left: 30px;
356
+ background: #6c757d;
357
+ color: white;
358
+ width: 60px;
359
+ height: 60px;
360
+ border-radius: 50%;
361
+ display: flex;
362
+ align-items: center;
363
+ justify-content: center;
364
+ font-size: 1.5rem;
365
+ box-shadow: 0 5px 15px rgba(108, 117, 125, 0.3);
366
+ transition: all 0.3s ease;
367
+ z-index: 1000;
368
+ cursor: pointer;
369
+ }
370
+
371
+ .visit-history-float:hover {
372
+ transform: scale(1.1);
373
+ background: #495057;
374
+ }
375
+
376
+ .dark-mode-toggle {
377
+ position: fixed;
378
+ top: 100px;
379
+ right: 30px;
380
+ background: white;
381
+ border: none;
382
+ border-radius: 50%;
383
+ width: 50px;
384
+ height: 50px;
385
+ display: flex;
386
+ align-items: center;
387
+ justify-content: center;
388
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
389
+ z-index: 1000;
390
+ transition: all 0.3s ease;
391
+ }
392
+
393
+ .dark-mode-toggle:hover {
394
+ transform: scale(1.1);
395
+ }
396
+
397
+ /* Dark mode styles */
398
+ body.dark-mode {
399
+ background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
400
+ color: #f1f5f9;
401
+ }
402
+
403
+ body.dark-mode .navbar {
404
+ background: rgba(30, 41, 59, 0.95) !important;
405
+ }
406
+
407
+ body.dark-mode .property-card,
408
+ body.dark-mode .search-container,
409
+ body.dark-mode .recommendations-section,
410
+ body.dark-mode .floating-email {
411
+ background: #334155;
412
+ color: #f1f5f9;
413
+ }
414
+
415
+ body.dark-mode .property-title {
416
+ color: #f1f5f9;
417
+ }
418
+
419
+ body.dark-mode .stat-card {
420
+ background: #475569;
421
+ }
422
+
423
+ /* Responsive design */
424
+ @media (max-width: 768px) {
425
+ .hero-title {
426
+ font-size: 2.5rem;
427
+ }
428
+
429
+ .property-grid {
430
+ grid-template-columns: 1fr;
431
+ }
432
+
433
+ .floating-email {
434
+ bottom: 20px;
435
+ right: 20px;
436
+ left: 20px;
437
+ max-width: none;
438
+ }
439
+
440
+ .whatsapp-float {
441
+ bottom: 20px;
442
+ left: 20px;
443
+ }
444
+ }
445
+ </style>
446
+ </head>
447
+ <body>
448
+ <!-- Navigation -->
449
+ <nav class="navbar navbar-expand-lg navbar-light">
450
+ <div class="container">
451
+ <a class="navbar-brand fw-bold" href="#">
452
+ <i class="fas fa-home me-2"></i>
453
+ Real Estate Lead Engine
454
+ </a>
455
+
456
+ <div class="navbar-nav ms-auto">
457
+ <a class="nav-link" href="#properties">Properties</a>
458
+ <a class="nav-link" href="#recommendations">Recommendations</a>
459
+ <a class="nav-link" href="#" onclick="showVisitHistory()">Visit History</a>
460
+ <a class="nav-link" href="/analytics">Analytics</a>
461
+ </div>
462
+ </div>
463
+ </nav>
464
+
465
+ <!-- Hero Section -->
466
+ <section class="hero-section">
467
+ <div class="container">
468
+ <h1 class="hero-title">Discover Your Perfect Property</h1>
469
+ <p class="hero-subtitle">Explore thousands of properties with smart recommendations based on your preferences</p>
470
+
471
+ <div class="search-container">
472
+ <div class="row">
473
+ <div class="col-md-3">
474
+ <select class="form-select" id="propertyType">
475
+ <option value="">All Property Types</option>
476
+ <option value="Flat">Flat</option>
477
+ <option value="Villa">Villa</option>
478
+ <option value="House">House</option>
479
+ <option value="Apartment">Apartment</option>
480
+ </select>
481
+ </div>
482
+ <div class="col-md-3">
483
+ <input type="number" class="form-control" id="minPrice" placeholder="Min Price">
484
+ </div>
485
+ <div class="col-md-3">
486
+ <input type="number" class="form-control" id="maxPrice" placeholder="Max Price">
487
+ </div>
488
+ <div class="col-md-3">
489
+ <button class="btn btn-primary w-100" onclick="searchProperties()">
490
+ <i class="fas fa-search me-2"></i>Search
491
+ </button>
492
+ </div>
493
+ </div>
494
+ </div>
495
+ </div>
496
+ </section>
497
+
498
+ <!-- Testing Controls -->
499
+ <div class="container mb-4">
500
+ <div class="row">
501
+ <div class="col-12">
502
+ <div class="alert alert-info">
503
+ <h6>πŸ§ͺ Testing Controls:</h6>
504
+ <button class="btn btn-sm btn-danger me-2" onclick="clearTrackingData()">
505
+ <i class="fas fa-trash"></i> Clear Data
506
+ </button>
507
+ <button class="btn btn-sm btn-warning me-2" onclick="testModal()">
508
+ <i class="fas fa-eye"></i> Test Modal
509
+ </button>
510
+ <button class="btn btn-sm btn-secondary me-2" onclick="testPropertyClick()">
511
+ <i class="fas fa-mouse-pointer"></i> Test Click
512
+ </button>
513
+ <button class="btn btn-sm btn-info me-2" onclick="showTrackingData()">
514
+ <i class="fas fa-chart-bar"></i> Show Data
515
+ </button>
516
+ <small class="d-block mt-2">
517
+ <strong>Expected Behavior:</strong> Card clicks = tracked + opens modal, "View Details" = same behavior, NO DUPLICATES
518
+ </small>
519
+ </div>
520
+ </div>
521
+ </div>
522
+ </div>
523
+
524
+ <!-- Properties Section -->
525
+ <section class="container" id="properties">
526
+ <div class="loading-spinner" id="loadingSpinner">
527
+ <div class="spinner"></div>
528
+ <p class="mt-3">Loading properties...</p>
529
+ </div>
530
+
531
+ <div class="property-grid" id="propertyGrid"></div>
532
+
533
+ <div class="text-center mt-4">
534
+ <button class="btn btn-outline-primary" id="loadMoreBtn" onclick="loadMoreProperties()" style="display: none;">
535
+ Load More Properties
536
+ </button>
537
+ </div>
538
+ </section>
539
+
540
+ <!-- Recommendations Section -->
541
+ <section class="container" id="recommendations">
542
+ <div class="recommendations-section">
543
+ <h2 class="section-title">
544
+ <i class="fas fa-star me-2"></i>
545
+ Personalized Recommendations
546
+ </h2>
547
+
548
+ <div class="stats-grid">
549
+ <div class="stat-card">
550
+ <div class="stat-number" id="propertiesViewed">0</div>
551
+ <div class="stat-label">Properties Viewed</div>
552
+ </div>
553
+ <div class="stat-card">
554
+ <div class="stat-number" id="timeSpent">0</div>
555
+ <div class="stat-label">Minutes Spent</div>
556
+ </div>
557
+ <div class="stat-card">
558
+ <div class="stat-number" id="leadScore">0</div>
559
+ <div class="stat-label">Lead Score</div>
560
+ </div>
561
+ <div class="stat-card">
562
+ <div class="stat-number" id="visitRequests">0</div>
563
+ <div class="stat-label">Visit Requests</div>
564
+ </div>
565
+ </div>
566
+
567
+ <div class="property-grid" id="recommendationsGrid"></div>
568
+ </div>
569
+ </section>
570
+
571
+ <!-- Floating Email Capture -->
572
+ <div class="floating-email" id="floatingEmail">
573
+ <div class="email-header">
574
+ <div class="email-icon">
575
+ <i class="fas fa-envelope"></i>
576
+ </div>
577
+ <div>
578
+ <h5 class="mb-0">Get Your Perfect Matches!</h5>
579
+ <small class="text-muted">Receive personalized property recommendations</small>
580
+ </div>
581
+ <button type="button" class="btn-close" onclick="hideFloatingEmail()" style="position: absolute; top: 10px; right: 10px;"></button>
582
+ </div>
583
+
584
+ <form id="emailForm">
585
+ <div class="mb-3">
586
+ <input type="email" class="form-control" id="userEmail" placeholder="Enter your email" required>
587
+ </div>
588
+ <button type="submit" class="btn btn-primary w-100">
589
+ <i class="fas fa-paper-plane me-2"></i>
590
+ Send Recommendations
591
+ </button>
592
+ </form>
593
+ </div>
594
+
595
+ <!-- WhatsApp Float -->
596
+ <a href="https://wa.me/919876543210" class="whatsapp-float" target="_blank">
597
+ <i class="fab fa-whatsapp"></i>
598
+ </a>
599
+
600
+ <!-- Visit History Float -->
601
+ <div class="visit-history-float" onclick="showVisitHistory()">
602
+ <i class="fas fa-history"></i>
603
+ </div>
604
+
605
+ <!-- Dark Mode Toggle -->
606
+ <button class="dark-mode-toggle" onclick="toggleDarkMode()">
607
+ <i class="fas fa-moon" id="darkModeIcon"></i>
608
+ </button>
609
+
610
+ <!-- Property Details Modal -->
611
+ <div class="modal fade" id="propertyDetailsModal" tabindex="-1" aria-labelledby="propertyDetailsModalLabel" aria-hidden="true">
612
+ <div class="modal-dialog modal-xl">
613
+ <div class="modal-content">
614
+ <div class="modal-header">
615
+ <h5 class="modal-title" id="propertyDetailsModalLabel">
616
+ <i class="fas fa-home me-2"></i>Property Details
617
+ </h5>
618
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
619
+ </div>
620
+ <div class="modal-body">
621
+ <div class="row">
622
+ <div class="col-md-8">
623
+ <div id="propertyImages" class="mb-4">
624
+ <!-- Property images will be loaded here -->
625
+ </div>
626
+ <div id="propertyDescription" class="mb-4">
627
+ <!-- Property description will be loaded here -->
628
+ </div>
629
+ </div>
630
+ <div class="col-md-4">
631
+ <div class="property-details-sidebar">
632
+ <div class="card">
633
+ <div class="card-body">
634
+ <h5 id="detailPropertyName" class="card-title"></h5>
635
+ <h4 id="detailPropertyPrice" class="text-primary mb-3"></h4>
636
+ <div class="property-meta mb-3">
637
+ <div class="row">
638
+ <div class="col-6">
639
+ <small class="text-muted">Type</small>
640
+ <p id="detailPropertyType" class="mb-0"></p>
641
+ </div>
642
+ <div class="col-6">
643
+ <small class="text-muted">Location</small>
644
+ <p id="detailPropertyAddress" class="mb-0"></p>
645
+ </div>
646
+ </div>
647
+ <div class="row mt-2">
648
+ <div class="col-4">
649
+ <small class="text-muted">Beds</small>
650
+ <p id="detailPropertyBeds" class="mb-0"></p>
651
+ </div>
652
+ <div class="col-4">
653
+ <small class="text-muted">Baths</small>
654
+ <p id="detailPropertyBaths" class="mb-0"></p>
655
+ </div>
656
+ <div class="col-4">
657
+ <small class="text-muted">Size</small>
658
+ <p id="detailPropertySize" class="mb-0"></p>
659
+ </div>
660
+ </div>
661
+ </div>
662
+ <div class="property-features mb-3">
663
+ <h6>Features</h6>
664
+ <div id="detailPropertyFeatures"></div>
665
+ </div>
666
+ <div class="d-grid gap-2">
667
+ <button type="button" class="btn btn-primary" onclick="openVisitModalFromDetails()">
668
+ <i class="fas fa-calendar-check me-2"></i>Schedule Visit
669
+ </button>
670
+ <button type="button" class="btn btn-outline-primary" onclick="addToFavorites()">
671
+ <i class="fas fa-heart me-2"></i>Add to Favorites
672
+ </button>
673
+ </div>
674
+ </div>
675
+ </div>
676
+ </div>
677
+ </div>
678
+ </div>
679
+ </div>
680
+ <div class="modal-footer">
681
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
682
+ <i class="fas fa-times me-2"></i>Close
683
+ </button>
684
+ <button type="button" class="btn btn-success" onclick="openVisitModalFromDetails()">
685
+ <i class="fas fa-calendar-check me-2"></i>Schedule Visit
686
+ </button>
687
+ <button type="button" class="btn btn-primary" onclick="shareProperty()">
688
+ <i class="fas fa-share me-2"></i>Share
689
+ </button>
690
+ </div>
691
+ </div>
692
+ </div>
693
+ </div>
694
+
695
+ <!-- Visit Modal -->
696
+ <div class="modal fade" id="visitModal" tabindex="-1" aria-labelledby="visitModalLabel" aria-hidden="true">
697
+ <div class="modal-dialog modal-lg">
698
+ <div class="modal-content">
699
+ <div class="modal-header">
700
+ <h5 class="modal-title" id="visitModalLabel">
701
+ <i class="fas fa-calendar-check me-2"></i>Schedule Property Visit
702
+ </h5>
703
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
704
+ </div>
705
+ <div class="modal-body">
706
+ <div class="row">
707
+ <div class="col-md-6">
708
+ <div class="property-summary mb-4">
709
+ <h6 class="text-muted mb-2">Property Details</h6>
710
+ <div class="property-info">
711
+ <h5 id="modalPropertyName" class="mb-2"></h5>
712
+ <p class="text-primary mb-1" id="modalPropertyPrice"></p>
713
+ <p class="text-muted mb-1" id="modalPropertyType"></p>
714
+ <p class="text-muted mb-3" id="modalPropertyAddress"></p>
715
+ </div>
716
+ </div>
717
+ </div>
718
+ <div class="col-md-6">
719
+ <form id="visitForm">
720
+ <div class="mb-3">
721
+ <label for="visitorName" class="form-label">Your Name *</label>
722
+ <input type="text" class="form-control" id="visitorName" required>
723
+ </div>
724
+ <div class="mb-3">
725
+ <label for="visitorEmail" class="form-label">Email Address *</label>
726
+ <input type="email" class="form-control" id="visitorEmail" required>
727
+ </div>
728
+ <div class="mb-3">
729
+ <label for="visitorPhone" class="form-label">Phone Number *</label>
730
+ <input type="tel" class="form-control" id="visitorPhone" required>
731
+ </div>
732
+ <div class="row">
733
+ <div class="col-md-6">
734
+ <div class="mb-3">
735
+ <label for="visitDate" class="form-label">Preferred Date *</label>
736
+ <input type="date" class="form-control" id="visitDate" required>
737
+ </div>
738
+ </div>
739
+ <div class="col-md-6">
740
+ <div class="mb-3">
741
+ <label for="visitTime" class="form-label">Preferred Time *</label>
742
+ <select class="form-select" id="visitTime" required>
743
+ <option value="">Select Time</option>
744
+ <option value="09:00">09:00 AM</option>
745
+ <option value="10:00">10:00 AM</option>
746
+ <option value="11:00">11:00 AM</option>
747
+ <option value="12:00">12:00 PM</option>
748
+ <option value="14:00">02:00 PM</option>
749
+ <option value="15:00">03:00 PM</option>
750
+ <option value="16:00">04:00 PM</option>
751
+ <option value="17:00">05:00 PM</option>
752
+ <option value="18:00">06:00 PM</option>
753
+ </select>
754
+ </div>
755
+ </div>
756
+ </div>
757
+ <div class="mb-3">
758
+ <label for="visitNotes" class="form-label">Additional Notes</label>
759
+ <textarea class="form-control" id="visitNotes" rows="3" placeholder="Any specific requirements or questions..."></textarea>
760
+ </div>
761
+ <div class="mb-3">
762
+ <div class="form-check">
763
+ <input class="form-check-input" type="checkbox" id="agreeTerms" required>
764
+ <label class="form-check-label" for="agreeTerms">
765
+ I agree to receive communications about this property visit
766
+ </label>
767
+ </div>
768
+ </div>
769
+ </form>
770
+ </div>
771
+ </div>
772
+ </div>
773
+ <div class="modal-footer">
774
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
775
+ <i class="fas fa-times me-2"></i>Cancel
776
+ </button>
777
+ <button type="button" class="btn btn-primary" onclick="submitVisitRequest()">
778
+ <i class="fas fa-calendar-plus me-2"></i>Schedule Visit
779
+ </button>
780
+ </div>
781
+ </div>
782
+ </div>
783
+ </div>
784
+
785
+ <!-- Bootstrap JS -->
786
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
787
+
788
+ <!-- Custom JS -->
789
+ <script>
790
+ // Global variables
791
+ let currentPage = 1;
792
+ let allProperties = [];
793
+ let userSessionId = null;
794
+ let userEmail = null;
795
+ let isDarkMode = false;
796
+
797
+ // Initialize the application
798
+ document.addEventListener('DOMContentLoaded', function() {
799
+ loadProperties();
800
+ initializeDetailedViewTracking();
801
+
802
+ // Show email form immediately
803
+ showEmailFormImmediately();
804
+
805
+ // Track page load
806
+ trackActivity('page_load', {
807
+ page: 'home',
808
+ timestamp: new Date().toISOString()
809
+ });
810
+ });
811
+
812
+ function initializeApp() {
813
+ // Generate session ID if not exists
814
+ userSessionId = localStorage.getItem('userSessionId') || generateSessionId();
815
+ localStorage.setItem('userSessionId', userSessionId);
816
+
817
+ // Load dark mode preference
818
+ isDarkMode = localStorage.getItem('darkMode') === 'true';
819
+ if (isDarkMode) {
820
+ document.body.classList.add('dark-mode');
821
+ document.getElementById('darkModeIcon').className = 'fas fa-sun';
822
+ }
823
+
824
+ // Load properties
825
+ loadProperties();
826
+
827
+ // Show floating email after 30 seconds
828
+ setTimeout(() => {
829
+ showFloatingEmail();
830
+ }, 30000);
831
+
832
+ // Track page view
833
+ trackActivity('page_view', { page: 'home' });
834
+ }
835
+
836
+ function generateSessionId() {
837
+ return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
838
+ }
839
+
840
+ async function loadProperties(page = 1, append = false) {
841
+ try {
842
+ showLoading(true);
843
+
844
+ const params = new URLSearchParams({
845
+ page: page,
846
+ page_size: 20
847
+ });
848
+
849
+ const response = await fetch(`/api/properties?${params}`);
850
+ const data = await response.json();
851
+
852
+ if (data.properties) {
853
+ if (append) {
854
+ allProperties = allProperties.concat(data.properties);
855
+ } else {
856
+ allProperties = data.properties;
857
+ }
858
+
859
+ renderProperties(allProperties);
860
+
861
+ // Show/hide load more button
862
+ const loadMoreBtn = document.getElementById('loadMoreBtn');
863
+ if (data.page < data.total_pages) {
864
+ loadMoreBtn.style.display = 'block';
865
+ } else {
866
+ loadMoreBtn.style.display = 'none';
867
+ }
868
+ }
869
+
870
+ } catch (error) {
871
+ console.error('Error loading properties:', error);
872
+ showError('Failed to load properties');
873
+ } finally {
874
+ showLoading(false);
875
+ }
876
+ }
877
+
878
+ function renderProperties(properties) {
879
+ const grid = document.getElementById('propertyGrid');
880
+ grid.innerHTML = '';
881
+
882
+ properties.forEach(property => {
883
+ const card = createPropertyCard(property);
884
+ grid.appendChild(card);
885
+ });
886
+
887
+ // Initialize detailed view tracking after properties are rendered
888
+ setTimeout(() => {
889
+ addDetailedViewTracking();
890
+ }, 500);
891
+ }
892
+
893
+ function createPropertyCard(property) {
894
+ const card = document.createElement('div');
895
+ card.className = 'property-card';
896
+ card.onclick = () => viewProperty(property);
897
+
898
+ const features = property.features || [];
899
+ // Use property images if available, otherwise show no image
900
+ const images = property.propertyImages || [];
901
+
902
+ card.innerHTML = `
903
+ <div class="position-relative">
904
+ ${images.length > 0 ? `
905
+ <img src="${images[0]}"
906
+ alt="${property.propertyName}"
907
+ class="property-image"
908
+ onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
909
+ ` : ''}
910
+ <div class="property-badge">${property.typeName || 'Property'}</div>
911
+ ${images.length === 0 ? `
912
+ <div class="no-image-placeholder" style="width: 100%; height: 250px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 18px;">
913
+ <i class="fas fa-home me-2"></i>${property.propertyName || 'Property'}
914
+ </div>
915
+ ` : ''}
916
+ </div>
917
+ <div class="property-content">
918
+ <h5 class="property-title">${property.propertyName || 'Property'}</h5>
919
+ <div class="property-price">β‚Ή${(property.marketValue || 0).toLocaleString()}</div>
920
+ <div class="property-location">
921
+ <i class="fas fa-map-marker-alt me-2"></i>
922
+ ${property.address || 'Location not specified'}
923
+ </div>
924
+ <div class="property-features">
925
+ ${features.slice(0, 3).map(feature =>
926
+ `<span class="feature-tag available">${feature}</span>`
927
+ ).join('')}
928
+ ${features.length > 3 ? `<span class="feature-tag">+${features.length - 3} more</span>` : ''}
929
+ </div>
930
+ <div class="property-actions">
931
+ <button class="btn-action btn-primary-custom" onclick="event.stopPropagation(); scheduleVisit('${property.id}', '${property.propertyName}', '${property.marketValue}', '${property.typeName}', '${property.address}')">
932
+ <i class="fas fa-calendar me-2"></i>Schedule Visit
933
+ </button>
934
+ <button class="btn-action btn-outline-custom" onclick="event.stopPropagation(); contactAgent('${property.id}')">
935
+ <i class="fas fa-phone me-2"></i>Contact
936
+ </button>
937
+ </div>
938
+ </div>
939
+ `;
940
+
941
+ return card;
942
+ }
943
+
944
+ function viewProperty(property) {
945
+ console.log('πŸ” Property clicked:', property.propertyName, 'ID:', property.id);
946
+
947
+ // Track property click (not just view)
948
+ trackActivity('property_click', {
949
+ property: property
950
+ });
951
+
952
+ // Track property type preference
953
+ if (property.typeName) {
954
+ trackActivity('property_type_select', { propertyType: property.typeName });
955
+ }
956
+
957
+ // Track price range preference
958
+ if (property.marketValue) {
959
+ const priceRange = getPriceRange(property.marketValue);
960
+ trackActivity('price_range_select', { priceRange: priceRange });
961
+ }
962
+
963
+ // Show property details modal
964
+ showPropertyDetailsModal(property);
965
+
966
+ console.log('βœ… Property click tracked and modal opened for:', property.propertyName);
967
+ }
968
+
969
+ function scheduleVisit(propertyId, propertyName, propertyPrice, propertyType, propertyAddress) {
970
+ // Open visit modal directly
971
+ openVisitModal(propertyId, propertyName, propertyPrice, propertyType, propertyAddress);
972
+ }
973
+
974
+ function contactAgent(propertyId) {
975
+ trackActivity('contact_agent', { property_id: propertyId });
976
+ // Implement contact agent functionality
977
+ alert('Agent contact feature coming soon!');
978
+ }
979
+
980
+ // Global variables for visit modal
981
+ let currentVisitProperty = null;
982
+ let modalOpenTime = null;
983
+ let visitModal = null;
984
+ let visitHistory = [];
985
+ let userPreferences = {};
986
+
987
+ function openVisitModal(propertyId, propertyName, propertyPrice, propertyType, propertyAddress) {
988
+ // Store current property info
989
+ currentVisitProperty = {
990
+ id: propertyId,
991
+ name: propertyName,
992
+ price: propertyPrice,
993
+ type: propertyType,
994
+ address: propertyAddress,
995
+ timestamp: new Date().toISOString()
996
+ };
997
+
998
+ // Set modal content
999
+ document.getElementById('modalPropertyName').textContent = propertyName || 'Property';
1000
+ document.getElementById('modalPropertyPrice').textContent = `β‚Ή${parseInt(propertyPrice || 0).toLocaleString()}`;
1001
+ document.getElementById('modalPropertyType').textContent = propertyType || 'Property';
1002
+ document.getElementById('modalPropertyAddress').textContent = propertyAddress || 'Location not specified';
1003
+
1004
+ // Set minimum date to today
1005
+ const today = new Date().toISOString().split('T')[0];
1006
+ document.getElementById('visitDate').min = today;
1007
+ document.getElementById('visitDate').value = today;
1008
+
1009
+ // Load user preferences from localStorage
1010
+ loadUserPreferences();
1011
+
1012
+ // Pre-fill form with saved data if available
1013
+ prefillVisitForm();
1014
+
1015
+ // Track modal open
1016
+ trackActivity('visit_modal_open', {
1017
+ property_id: propertyId,
1018
+ property_name: propertyName,
1019
+ property_price: propertyPrice,
1020
+ property_type: propertyType,
1021
+ property_address: propertyAddress
1022
+ });
1023
+
1024
+ // Store visit attempt in localStorage
1025
+ storeVisitAttempt(currentVisitProperty);
1026
+
1027
+ // Record modal open time
1028
+ modalOpenTime = Date.now();
1029
+
1030
+ // Show modal
1031
+ visitModal = new bootstrap.Modal(document.getElementById('visitModal'));
1032
+ visitModal.show();
1033
+
1034
+ // Track modal duration when closed
1035
+ document.getElementById('visitModal').addEventListener('hidden.bs.modal', function() {
1036
+ if (modalOpenTime) {
1037
+ const duration = Date.now() - modalOpenTime;
1038
+ trackActivity('visit_modal_duration', {
1039
+ property_id: propertyId,
1040
+ duration: duration,
1041
+ modal_action: 'closed'
1042
+ });
1043
+
1044
+ // Store modal interaction data
1045
+ storeModalInteraction({
1046
+ property_id: propertyId,
1047
+ duration: duration,
1048
+ action: 'closed',
1049
+ timestamp: new Date().toISOString()
1050
+ });
1051
+
1052
+ modalOpenTime = null;
1053
+ }
1054
+ });
1055
+ }
1056
+
1057
+ function submitVisitRequest() {
1058
+ // Get form data
1059
+ const formData = {
1060
+ visitorName: document.getElementById('visitorName').value,
1061
+ visitorEmail: document.getElementById('visitorEmail').value,
1062
+ visitorPhone: document.getElementById('visitorPhone').value,
1063
+ visitDate: document.getElementById('visitDate').value,
1064
+ visitTime: document.getElementById('visitTime').value,
1065
+ visitNotes: document.getElementById('visitNotes').value,
1066
+ agreeTerms: document.getElementById('agreeTerms').checked
1067
+ };
1068
+
1069
+ // Validate form
1070
+ if (!formData.visitorName || !formData.visitorEmail || !formData.visitorPhone ||
1071
+ !formData.visitDate || !formData.visitTime || !formData.agreeTerms) {
1072
+ showNotification('Please fill in all required fields', 'warning');
1073
+ return;
1074
+ }
1075
+
1076
+ // Calculate modal duration
1077
+ const duration = modalOpenTime ? Date.now() - modalOpenTime : 0;
1078
+
1079
+ // Store user preferences in localStorage
1080
+ storeUserPreferences(formData);
1081
+
1082
+ // Store visit request in localStorage
1083
+ const visitRequest = {
1084
+ ...currentVisitProperty,
1085
+ visitor_data: formData,
1086
+ modal_duration: duration,
1087
+ submitted_at: new Date().toISOString(),
1088
+ status: 'submitted'
1089
+ };
1090
+ storeVisitRequest(visitRequest);
1091
+
1092
+ // Track visit request submission
1093
+ trackActivity('visit_request_submitted', {
1094
+ property_id: currentVisitProperty.id,
1095
+ property_name: currentVisitProperty.name,
1096
+ property_price: currentVisitProperty.price,
1097
+ property_type: currentVisitProperty.type,
1098
+ property_address: currentVisitProperty.address,
1099
+ visitor_name: formData.visitorName,
1100
+ visitor_email: formData.visitorEmail,
1101
+ visitor_phone: formData.visitorPhone,
1102
+ visit_date: formData.visitDate,
1103
+ visit_time: formData.visitTime,
1104
+ visit_notes: formData.visitNotes,
1105
+ modal_duration: duration,
1106
+ form_data: formData
1107
+ });
1108
+
1109
+ // Update user email for future tracking
1110
+ userEmail = formData.visitorEmail;
1111
+
1112
+ // Show success message
1113
+ showNotification('Visit request submitted successfully! We will contact you soon.', 'success');
1114
+
1115
+ // Close modal
1116
+ visitModal.hide();
1117
+
1118
+ // Reset form
1119
+ document.getElementById('visitForm').reset();
1120
+
1121
+ // Send confirmation email (if email service is configured)
1122
+ sendVisitConfirmationEmail(formData);
1123
+
1124
+ // Update visit history display
1125
+ updateVisitHistoryDisplay();
1126
+ }
1127
+
1128
+ function sendVisitConfirmationEmail(formData) {
1129
+ // This would integrate with your email service
1130
+ console.log('Sending visit confirmation email to:', formData.visitorEmail);
1131
+
1132
+ // You can implement email sending here or call your backend API
1133
+ fetch('/api/send-visit-confirmation', {
1134
+ method: 'POST',
1135
+ headers: {
1136
+ 'Content-Type': 'application/json',
1137
+ },
1138
+ body: JSON.stringify({
1139
+ visitor_data: formData,
1140
+ property_data: currentVisitProperty
1141
+ })
1142
+ }).catch(error => {
1143
+ console.error('Error sending visit confirmation:', error);
1144
+ });
1145
+ }
1146
+
1147
+ function bookVisit(propertyId) {
1148
+ // Fallback function for backward compatibility
1149
+ trackActivity('book_visit', { property_id: propertyId });
1150
+ showNotification('Please use the Visit button to schedule a property visit', 'info');
1151
+ }
1152
+
1153
+ // ===== LOCALSTORAGE FUNCTIONS =====
1154
+
1155
+ function storeUserPreferences(formData) {
1156
+ const preferences = {
1157
+ visitorName: formData.visitorName,
1158
+ visitorEmail: formData.visitorEmail,
1159
+ visitorPhone: formData.visitorPhone,
1160
+ lastUpdated: new Date().toISOString()
1161
+ };
1162
+ localStorage.setItem('userPreferences', JSON.stringify(preferences));
1163
+ }
1164
+
1165
+ function loadUserPreferences() {
1166
+ const saved = localStorage.getItem('userPreferences');
1167
+ if (saved) {
1168
+ userPreferences = JSON.parse(saved);
1169
+ }
1170
+ return userPreferences;
1171
+ }
1172
+
1173
+ function prefillVisitForm() {
1174
+ if (userPreferences.visitorName) {
1175
+ document.getElementById('visitorName').value = userPreferences.visitorName;
1176
+ }
1177
+ if (userPreferences.visitorEmail) {
1178
+ document.getElementById('visitorEmail').value = userPreferences.visitorEmail;
1179
+ }
1180
+ if (userPreferences.visitorPhone) {
1181
+ document.getElementById('visitorPhone').value = userPreferences.visitorPhone;
1182
+ }
1183
+ }
1184
+
1185
+ function storeVisitAttempt(propertyData) {
1186
+ const attempts = JSON.parse(localStorage.getItem('visitAttempts') || '[]');
1187
+ attempts.push({
1188
+ ...propertyData,
1189
+ attempt_time: new Date().toISOString()
1190
+ });
1191
+ localStorage.setItem('visitAttempts', JSON.stringify(attempts));
1192
+ }
1193
+
1194
+ function storeVisitRequest(visitRequest) {
1195
+ const requests = JSON.parse(localStorage.getItem('visitRequests') || '[]');
1196
+ requests.push(visitRequest);
1197
+ localStorage.setItem('visitRequests', JSON.stringify(requests));
1198
+ }
1199
+
1200
+ function storeModalInteraction(interactionData) {
1201
+ const interactions = JSON.parse(localStorage.getItem('modalInteractions') || '[]');
1202
+ interactions.push(interactionData);
1203
+ localStorage.setItem('modalInteractions', JSON.stringify(interactions));
1204
+ }
1205
+
1206
+ function getVisitHistory() {
1207
+ return JSON.parse(localStorage.getItem('visitRequests') || '[]');
1208
+ }
1209
+
1210
+ function getVisitAttempts() {
1211
+ return JSON.parse(localStorage.getItem('visitAttempts') || '[]');
1212
+ }
1213
+
1214
+ function getModalInteractions() {
1215
+ return JSON.parse(localStorage.getItem('modalInteractions') || '[]');
1216
+ }
1217
+
1218
+ function clearVisitHistory() {
1219
+ localStorage.removeItem('visitRequests');
1220
+ localStorage.removeItem('visitAttempts');
1221
+ localStorage.removeItem('modalInteractions');
1222
+ showNotification('Visit history cleared', 'info');
1223
+ }
1224
+
1225
+ function updateVisitHistoryDisplay() {
1226
+ const history = getVisitHistory();
1227
+ const attempts = getVisitAttempts();
1228
+
1229
+ // Update analytics if available
1230
+ if (typeof updateVisitStats === 'function') {
1231
+ updateVisitStats(history, attempts);
1232
+ }
1233
+ }
1234
+
1235
+ function exportVisitData() {
1236
+ const data = {
1237
+ visitRequests: getVisitHistory(),
1238
+ visitAttempts: getVisitAttempts(),
1239
+ modalInteractions: getModalInteractions(),
1240
+ userPreferences: loadUserPreferences(),
1241
+ exportDate: new Date().toISOString()
1242
+ };
1243
+
1244
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
1245
+ const url = URL.createObjectURL(blob);
1246
+ const a = document.createElement('a');
1247
+ a.href = url;
1248
+ a.download = `visit-data-${new Date().toISOString().split('T')[0]}.json`;
1249
+ document.body.appendChild(a);
1250
+ a.click();
1251
+ document.body.removeChild(a);
1252
+ URL.revokeObjectURL(url);
1253
+
1254
+ showNotification('Visit data exported successfully', 'success');
1255
+ }
1256
+
1257
+ function showVisitHistory() {
1258
+ const history = getVisitHistory();
1259
+ const attempts = getVisitAttempts();
1260
+
1261
+ let historyHtml = '<div class="visit-history-modal">';
1262
+ historyHtml += '<h4>Visit History</h4>';
1263
+
1264
+ if (history.length === 0 && attempts.length === 0) {
1265
+ historyHtml += '<p>No visit history found.</p>';
1266
+ } else {
1267
+ // Show submitted requests
1268
+ if (history.length > 0) {
1269
+ historyHtml += '<h5>Submitted Requests</h5>';
1270
+ history.forEach((request, index) => {
1271
+ historyHtml += `
1272
+ <div class="visit-item">
1273
+ <strong>${request.name}</strong><br>
1274
+ <small>Date: ${request.visitor_data.visit_date} at ${request.visitor_data.visit_time}</small><br>
1275
+ <small>Status: ${request.status}</small>
1276
+ </div>
1277
+ `;
1278
+ });
1279
+ }
1280
+
1281
+ // Show visit attempts
1282
+ if (attempts.length > 0) {
1283
+ historyHtml += '<h5>Visit Attempts</h5>';
1284
+ attempts.forEach((attempt, index) => {
1285
+ historyHtml += `
1286
+ <div class="visit-item">
1287
+ <strong>${attempt.name}</strong><br>
1288
+ <small>Attempted: ${new Date(attempt.attempt_time).toLocaleDateString()}</small>
1289
+ </div>
1290
+ `;
1291
+ });
1292
+ }
1293
+ }
1294
+
1295
+ historyHtml += '</div>';
1296
+
1297
+ // Show in modal or notification
1298
+ showNotification(historyHtml, 'info', 10000);
1299
+ }
1300
+
1301
+ // Initialize localStorage data on page load
1302
+ function initializeLocalStorage() {
1303
+ loadUserPreferences();
1304
+ updateVisitHistoryDisplay();
1305
+ }
1306
+
1307
+ // Call initialization
1308
+ initializeLocalStorage();
1309
+
1310
+ function searchProperties() {
1311
+ const propertyType = document.getElementById('propertyType').value;
1312
+ const minPrice = document.getElementById('minPrice').value;
1313
+ const maxPrice = document.getElementById('maxPrice').value;
1314
+
1315
+ // Track search
1316
+ trackActivity('property_search', {
1317
+ property_type: propertyType,
1318
+ min_price: minPrice,
1319
+ max_price: maxPrice
1320
+ });
1321
+
1322
+ // Filter properties
1323
+ let filtered = allProperties;
1324
+
1325
+ if (propertyType) {
1326
+ filtered = filtered.filter(p => p.typeName === propertyType);
1327
+ }
1328
+ if (minPrice) {
1329
+ filtered = filtered.filter(p => p.marketValue >= parseInt(minPrice));
1330
+ }
1331
+ if (maxPrice) {
1332
+ filtered = filtered.filter(p => p.marketValue <= parseInt(maxPrice));
1333
+ }
1334
+
1335
+ renderProperties(filtered);
1336
+ }
1337
+
1338
+ function loadMoreProperties() {
1339
+ currentPage++;
1340
+ loadProperties(currentPage, true);
1341
+ }
1342
+
1343
+ function showFloatingEmail() {
1344
+ const emailDiv = document.getElementById('floatingEmail');
1345
+ if (emailDiv) {
1346
+ emailDiv.classList.remove('hidden');
1347
+ emailDiv.classList.add('show');
1348
+ console.log('Email form shown');
1349
+ }
1350
+ }
1351
+
1352
+ function hideFloatingEmail() {
1353
+ const emailDiv = document.getElementById('floatingEmail');
1354
+ if (emailDiv) {
1355
+ emailDiv.classList.remove('show');
1356
+ emailDiv.classList.add('hidden');
1357
+ }
1358
+ }
1359
+
1360
+ // Show email form immediately on page load
1361
+ function showEmailFormImmediately() {
1362
+ setTimeout(() => {
1363
+ showFloatingEmail();
1364
+ }, 1000);
1365
+ }
1366
+
1367
+ function toggleDarkMode() {
1368
+ isDarkMode = !isDarkMode;
1369
+ document.body.classList.toggle('dark-mode', isDarkMode);
1370
+
1371
+ const icon = document.getElementById('darkModeIcon');
1372
+ icon.className = isDarkMode ? 'fas fa-sun' : 'fas fa-moon';
1373
+
1374
+ localStorage.setItem('darkMode', isDarkMode);
1375
+ }
1376
+
1377
+ function showLoading(show) {
1378
+ const spinner = document.getElementById('loadingSpinner');
1379
+ const grid = document.getElementById('propertyGrid');
1380
+
1381
+ if (show) {
1382
+ spinner.style.display = 'block';
1383
+ grid.style.display = 'none';
1384
+ } else {
1385
+ spinner.style.display = 'none';
1386
+ grid.style.display = 'grid';
1387
+ }
1388
+ }
1389
+
1390
+ function showError(message) {
1391
+ // Implement error notification
1392
+ console.error(message);
1393
+ }
1394
+
1395
+ // Show notification
1396
+ function showNotification(message, type = 'info') {
1397
+ const notification = document.createElement('div');
1398
+ notification.className = `alert alert-${type} alert-dismissible fade show`;
1399
+ notification.style.cssText = `
1400
+ position: fixed;
1401
+ top: 20px;
1402
+ right: 20px;
1403
+ z-index: 9999;
1404
+ min-width: 300px;
1405
+ max-width: 400px;
1406
+ `;
1407
+ notification.innerHTML = `
1408
+ ${message}
1409
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
1410
+ `;
1411
+
1412
+ document.body.appendChild(notification);
1413
+
1414
+ // Auto-remove after 3 seconds
1415
+ setTimeout(() => {
1416
+ if (notification.parentNode) {
1417
+ notification.remove();
1418
+ }
1419
+ }, 3000);
1420
+ }
1421
+
1422
+ // Track user activity
1423
+ function trackActivity(actionType, actionData = {}) {
1424
+ // Get or create session ID
1425
+ let sessionId = localStorage.getItem('userSessionId');
1426
+ if (!sessionId) {
1427
+ sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
1428
+ localStorage.setItem('userSessionId', sessionId);
1429
+ }
1430
+
1431
+ // Get current user tracking data
1432
+ let userTrackingData = JSON.parse(localStorage.getItem('userTrackingData') || '{}');
1433
+
1434
+ // Initialize tracking data structure if not exists
1435
+ if (!userTrackingData.clickedProperties) userTrackingData.clickedProperties = [];
1436
+ if (!userTrackingData.detailedViews) userTrackingData.detailedViews = [];
1437
+ if (!userTrackingData.searchHistory) userTrackingData.searchHistory = [];
1438
+ if (!userTrackingData.features) userTrackingData.features = [];
1439
+ if (!userTrackingData.priceRanges) userTrackingData.priceRanges = [];
1440
+ if (!userTrackingData.propertyTypes) userTrackingData.propertyTypes = [];
1441
+
1442
+ // Track based on action type
1443
+ switch (actionType) {
1444
+ case 'property_click':
1445
+ console.log('πŸ”΅ Property CLICK tracked:', actionData.property?.propertyName);
1446
+ showNotification(`βœ… Click tracked: ${actionData.property?.propertyName}`, 'success');
1447
+
1448
+ if (actionData.property) {
1449
+ // Check if this property is already in clickedProperties to avoid duplicates
1450
+ const existingClick = userTrackingData.clickedProperties.find(p => parseInt(p.id) === parseInt(actionData.property.id));
1451
+ if (!existingClick) {
1452
+ userTrackingData.clickedProperties.push({
1453
+ id: actionData.property.id,
1454
+ name: actionData.property.propertyName,
1455
+ type: actionData.property.typeName,
1456
+ price: actionData.property.marketValue,
1457
+ timestamp: new Date().toISOString()
1458
+ });
1459
+ console.log('βœ… Added to clickedProperties:', actionData.property.propertyName);
1460
+ } else {
1461
+ console.log('⚠️ Property already in clickedProperties:', actionData.property.propertyName);
1462
+ }
1463
+
1464
+ // Track property type (avoid duplicates)
1465
+ if (actionData.property.typeName) {
1466
+ if (!userTrackingData.propertyTypes.includes(actionData.property.typeName)) {
1467
+ userTrackingData.propertyTypes.push(actionData.property.typeName);
1468
+ }
1469
+ }
1470
+
1471
+ // Track price range (avoid duplicates)
1472
+ if (actionData.property.marketValue) {
1473
+ const price = actionData.property.marketValue;
1474
+ let priceRange = '';
1475
+ if (price <= 500000) priceRange = '0-500000';
1476
+ else if (price <= 1000000) priceRange = '500000-1000000';
1477
+ else if (price <= 2000000) priceRange = '1000000-2000000';
1478
+ else if (price <= 5000000) priceRange = '2000000-5000000';
1479
+ else if (price <= 10000000) priceRange = '5000000-10000000';
1480
+ else priceRange = '10000000+';
1481
+
1482
+ if (!userTrackingData.priceRanges.includes(priceRange)) {
1483
+ userTrackingData.priceRanges.push(priceRange);
1484
+ }
1485
+ }
1486
+ }
1487
+ break;
1488
+
1489
+ case 'click':
1490
+ if (actionData.property) {
1491
+ userTrackingData.clickedProperties.push({
1492
+ id: actionData.property.id,
1493
+ name: actionData.property.propertyName,
1494
+ type: actionData.property.typeName,
1495
+ price: actionData.property.marketValue,
1496
+ timestamp: new Date().toISOString()
1497
+ });
1498
+ }
1499
+ break;
1500
+
1501
+ case 'detailed_view':
1502
+ console.log('🟒 Property DETAILED VIEW tracked:', actionData.property?.propertyName, 'Duration:', actionData.duration, 'seconds');
1503
+ showNotification(`πŸ‘οΈ Detailed view tracked: ${actionData.property?.propertyName} (${actionData.duration}s)`, 'info');
1504
+
1505
+ if (actionData.property) {
1506
+ const existingView = userTrackingData.detailedViews.find(
1507
+ view => parseInt(view.propertyId) === parseInt(actionData.property.id)
1508
+ );
1509
+
1510
+ if (existingView) {
1511
+ existingView.totalDuration += actionData.duration || 0;
1512
+ existingView.viewCount += 1;
1513
+ existingView.lastViewed = new Date().toISOString();
1514
+ console.log('βœ… Updated existing detailed view:', actionData.property.propertyName, 'Duration:', actionData.duration, 'Total:', existingView.totalDuration);
1515
+ } else {
1516
+ userTrackingData.detailedViews.push({
1517
+ propertyId: actionData.property.id,
1518
+ propertyName: actionData.property.propertyName,
1519
+ propertyType: actionData.property.typeName,
1520
+ price: actionData.property.marketValue,
1521
+ totalDuration: actionData.duration || 0,
1522
+ viewCount: 1,
1523
+ lastViewed: new Date().toISOString()
1524
+ });
1525
+ console.log('βœ… Added new detailed view:', actionData.property.propertyName, 'Duration:', actionData.duration);
1526
+ }
1527
+ }
1528
+ break;
1529
+
1530
+ case 'search':
1531
+ userTrackingData.searchHistory.push({
1532
+ query: actionData.query || '',
1533
+ filters: actionData.filters || {},
1534
+ timestamp: new Date().toISOString()
1535
+ });
1536
+ break;
1537
+
1538
+ case 'feature_click':
1539
+ if (actionData.feature && !userTrackingData.features.includes(actionData.feature)) {
1540
+ userTrackingData.features.push(actionData.feature);
1541
+ }
1542
+ break;
1543
+
1544
+ case 'price_range_select':
1545
+ if (actionData.priceRange) {
1546
+ userTrackingData.priceRanges.push(actionData.priceRange);
1547
+ }
1548
+ break;
1549
+
1550
+ case 'property_type_select':
1551
+ if (actionData.propertyType) {
1552
+ userTrackingData.propertyTypes.push(actionData.propertyType);
1553
+ }
1554
+ break;
1555
+ }
1556
+
1557
+ // Save updated tracking data
1558
+ localStorage.setItem('userTrackingData', JSON.stringify(userTrackingData));
1559
+
1560
+ // Send to server
1561
+ fetch('/api/track', {
1562
+ method: 'POST',
1563
+ headers: {
1564
+ 'Content-Type': 'application/json',
1565
+ },
1566
+ body: JSON.stringify({
1567
+ session_id: sessionId,
1568
+ action_type: actionType,
1569
+ action_data: actionData,
1570
+ user_id: sessionId,
1571
+ email: userEmail
1572
+ })
1573
+ }).catch(error => {
1574
+ console.error('Error tracking activity:', error);
1575
+ });
1576
+ }
1577
+
1578
+ // Enhanced property click tracking
1579
+ function trackPropertyClick(property) {
1580
+ trackActivity('click', { property: property });
1581
+
1582
+ // Track property type preference
1583
+ if (property.typeName) {
1584
+ trackActivity('property_type_select', { propertyType: property.typeName });
1585
+ }
1586
+
1587
+ // Track price range preference
1588
+ if (property.marketValue) {
1589
+ const priceRange = getPriceRange(property.marketValue);
1590
+ trackActivity('price_range_select', { priceRange: priceRange });
1591
+ }
1592
+ }
1593
+
1594
+ // Track detailed view with duration
1595
+ function trackDetailedView(property, duration = 0) {
1596
+ trackActivity('detailed_view', {
1597
+ property: property,
1598
+ duration: duration
1599
+ });
1600
+ }
1601
+
1602
+ // Track search activity
1603
+ function trackSearch(query, filters = {}) {
1604
+ trackActivity('search', {
1605
+ query: query,
1606
+ filters: filters
1607
+ });
1608
+ }
1609
+
1610
+ // Track feature interest
1611
+ function trackFeatureInterest(feature) {
1612
+ trackActivity('feature_click', { feature: feature });
1613
+ }
1614
+
1615
+ // Get price range category
1616
+ function getPriceRange(price) {
1617
+ if (price <= 500000) return '0-500000';
1618
+ if (price <= 1000000) return '500000-1000000';
1619
+ if (price <= 2000000) return '1000000-2000000';
1620
+ if (price <= 5000000) return '2000000-5000000';
1621
+ if (price <= 10000000) return '5000000-10000000';
1622
+ return '10000000+';
1623
+ }
1624
+
1625
+ // Get user tracking data
1626
+ function getUserTrackingData() {
1627
+ return JSON.parse(localStorage.getItem('userTrackingData') || '{}');
1628
+ }
1629
+
1630
+ // Clear user tracking data
1631
+ function clearUserTrackingData() {
1632
+ localStorage.removeItem('userTrackingData');
1633
+ localStorage.removeItem('userSessionId');
1634
+ console.log('User tracking data cleared');
1635
+ }
1636
+
1637
+ // Export user tracking data
1638
+ function exportUserTrackingData() {
1639
+ const data = getUserTrackingData();
1640
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
1641
+ const url = URL.createObjectURL(blob);
1642
+ const a = document.createElement('a');
1643
+ a.href = url;
1644
+ a.download = 'user_tracking_data.json';
1645
+ document.body.appendChild(a);
1646
+ a.click();
1647
+ document.body.removeChild(a);
1648
+ URL.revokeObjectURL(url);
1649
+ }
1650
+
1651
+ // Enhanced property card creation with tracking
1652
+ function createPropertyCard(property) {
1653
+ const card = document.createElement('div');
1654
+ card.className = 'property-card';
1655
+ card.onclick = () => viewProperty(property);
1656
+
1657
+ const features = property.features || [];
1658
+ // Use property images if available, otherwise show no image
1659
+ const images = property.propertyImages || [];
1660
+
1661
+ card.innerHTML = `
1662
+ <div class="position-relative">
1663
+ ${images.length > 0 ? `
1664
+ <img src="${images[0]}"
1665
+ alt="${property.propertyName}"
1666
+ class="property-image"
1667
+ onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
1668
+ ` : ''}
1669
+ <div class="property-badge">${property.typeName || 'Property'}</div>
1670
+ ${images.length === 0 ? `
1671
+ <div class="no-image-placeholder" style="width: 100%; height: 250px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 18px;">
1672
+ <i class="fas fa-home me-2"></i>${property.propertyName || 'Property'}
1673
+ </div>
1674
+ ` : ''}
1675
+ </div>
1676
+ <div class="property-content">
1677
+ <h5 class="property-title">${property.propertyName || 'Property'}</h5>
1678
+ <div class="property-price">β‚Ή${(property.marketValue || 0).toLocaleString()}</div>
1679
+ <div class="property-location">
1680
+ <i class="fas fa-map-marker-alt me-2"></i>
1681
+ ${property.address || 'Location not specified'}
1682
+ </div>
1683
+ <div class="property-features">
1684
+ ${features.slice(0, 3).map(feature =>
1685
+ `<span class="feature-tag available">${feature}</span>`
1686
+ ).join('')}
1687
+ ${features.length > 3 ? `<span class="feature-tag">+${features.length - 3} more</span>` : ''}
1688
+ </div>
1689
+ <div class="property-actions">
1690
+ <button class="btn-action btn-primary-custom" onclick="event.stopPropagation(); scheduleVisit('${property.id}', '${property.propertyName}', '${property.marketValue}', '${property.typeName}', '${property.address}')">
1691
+ <i class="fas fa-calendar me-2"></i>Schedule Visit
1692
+ </button>
1693
+ <button class="btn-action btn-outline-custom" onclick="event.stopPropagation(); contactAgent('${property.id}')">
1694
+ <i class="fas fa-phone me-2"></i>Contact
1695
+ </button>
1696
+ </div>
1697
+ </div>
1698
+ `;
1699
+
1700
+ return card;
1701
+ }
1702
+
1703
+ // Enhanced visit request with tracking
1704
+ function requestVisit(propertyId) {
1705
+ const property = allProperties.find(p => p.id === propertyId);
1706
+ if (!property) return;
1707
+
1708
+ // Track visit request
1709
+ trackActivity('visit_request', {
1710
+ property: property,
1711
+ action: 'visit_request_initiated'
1712
+ });
1713
+
1714
+ // Show visit request modal
1715
+ showVisitRequestModal(property);
1716
+ }
1717
+
1718
+ // Enhanced view details with tracking
1719
+ function viewDetails(propertyId) {
1720
+ const property = allProperties.find(p => p.id === propertyId);
1721
+ if (!property) return;
1722
+
1723
+ // Track detailed view with default duration
1724
+ trackDetailedView(property, 10); // Assume 10 seconds for button click
1725
+
1726
+ // Show property details modal
1727
+ showPropertyDetailsModal(property);
1728
+ }
1729
+
1730
+ // Property details modal with duration tracking
1731
+ let propertyModalOpenTime = null;
1732
+ let currentPropertyInModal = null;
1733
+
1734
+ function showPropertyDetailsModal(property) {
1735
+ console.log('🟒 Opening modal for:', property.propertyName);
1736
+ console.log('🟒 Property data:', property);
1737
+
1738
+ currentPropertyInModal = property;
1739
+ propertyModalOpenTime = Date.now();
1740
+
1741
+ // Set modal content
1742
+ document.getElementById('detailPropertyName').textContent = property.propertyName || 'Property';
1743
+ document.getElementById('detailPropertyPrice').textContent = `β‚Ή${parseInt(property.marketValue || 0).toLocaleString()}`;
1744
+ document.getElementById('detailPropertyType').textContent = property.typeName || 'Property';
1745
+ document.getElementById('detailPropertyAddress').textContent = property.address || 'Location not specified';
1746
+ document.getElementById('detailPropertyBeds').textContent = property.beds || 'N/A';
1747
+ document.getElementById('detailPropertyBaths').textContent = property.baths || 'N/A';
1748
+ document.getElementById('detailPropertySize').textContent = `${property.totalSquareFeet || 0} sq ft`;
1749
+
1750
+ // Load property images
1751
+ const imagesContainer = document.getElementById('propertyImages');
1752
+ // Use property images if available
1753
+ const validImages = property.propertyImages || [];
1754
+
1755
+ if (validImages && validImages.length > 0) {
1756
+ imagesContainer.innerHTML = `
1757
+ <div id="propertyImageCarousel" class="carousel slide" data-bs-ride="carousel">
1758
+ <div class="carousel-inner">
1759
+ ${validImages.map((img, index) => `
1760
+ <div class="carousel-item ${index === 0 ? 'active' : ''}">
1761
+ <img src="${img}" class="d-block w-100" style="height: 400px; object-fit: cover;" alt="Property Image"
1762
+ onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
1763
+ <div class="text-center py-5" style="display: none; height: 400px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white;">
1764
+ <div>
1765
+ <i class="fas fa-image fa-3x mb-3"></i>
1766
+ <p class="mb-0">Image not available</p>
1767
+ </div>
1768
+ </div>
1769
+ </div>
1770
+ `).join('')}
1771
+ </div>
1772
+ ${validImages.length > 1 ? `
1773
+ <button class="carousel-control-prev" type="button" data-bs-target="#propertyImageCarousel" data-bs-slide="prev">
1774
+ <span class="carousel-control-prev-icon" aria-hidden="true"></span>
1775
+ <span class="visually-hidden">Previous</span>
1776
+ </button>
1777
+ <button class="carousel-control-next" type="button" data-bs-target="#propertyImageCarousel" data-bs-slide="next">
1778
+ <span class="carousel-control-next-icon" aria-hidden="true"></span>
1779
+ <span class="visually-hidden">Next</span>
1780
+ </button>
1781
+ ` : ''}
1782
+ </div>
1783
+ `;
1784
+ } else {
1785
+ imagesContainer.innerHTML = `
1786
+ <div class="text-center py-5">
1787
+ <i class="fas fa-image fa-3x text-muted mb-3"></i>
1788
+ <p class="text-muted">No images available</p>
1789
+ </div>
1790
+ `;
1791
+ }
1792
+
1793
+ // Load property description
1794
+ const descriptionContainer = document.getElementById('propertyDescription');
1795
+ descriptionContainer.innerHTML = `
1796
+ <h5>Description</h5>
1797
+ <p>${property.description || 'No description available'}</p>
1798
+ `;
1799
+
1800
+ // Load property features
1801
+ const featuresContainer = document.getElementById('detailPropertyFeatures');
1802
+ if (property.features && Object.keys(property.features).length > 0) {
1803
+ const featuresList = Object.keys(property.features).map(feature =>
1804
+ `<span class="badge bg-primary me-2 mb-2">${feature}</span>`
1805
+ ).join('');
1806
+ featuresContainer.innerHTML = featuresList;
1807
+ } else {
1808
+ featuresContainer.innerHTML = '<p class="text-muted">No features listed</p>';
1809
+ }
1810
+
1811
+ // Track modal open
1812
+ trackActivity('property_details_modal_open', {
1813
+ property_id: property.id,
1814
+ property_name: property.propertyName,
1815
+ property_type: property.typeName,
1816
+ property_price: property.marketValue,
1817
+ timestamp: new Date().toISOString()
1818
+ });
1819
+
1820
+ // Show modal
1821
+ const modalElement = document.getElementById('propertyDetailsModal');
1822
+ if (modalElement) {
1823
+ console.log('🟒 Modal element found:', modalElement);
1824
+ const propertyModal = new bootstrap.Modal(modalElement);
1825
+ propertyModal.show();
1826
+ console.log('βœ… Modal should now be visible');
1827
+
1828
+ // Show notification that modal opened
1829
+ showNotification(`πŸ” Modal opened: ${property.propertyName}`, 'info');
1830
+ } else {
1831
+ console.error('❌ Modal element not found!');
1832
+ showNotification('❌ Modal element not found! Check console for details.', 'danger');
1833
+ console.log('🟒 Available elements with "modal" in ID:');
1834
+ document.querySelectorAll('[id*="modal"]').forEach(el => {
1835
+ console.log(' -', el.id);
1836
+ });
1837
+ }
1838
+
1839
+ // Track modal duration when closed
1840
+ document.getElementById('propertyDetailsModal').addEventListener('hidden.bs.modal', function() {
1841
+ if (propertyModalOpenTime && currentPropertyInModal) {
1842
+ const duration = Date.now() - propertyModalOpenTime;
1843
+ const durationSeconds = Math.round(duration / 1000);
1844
+
1845
+ // Track detailed view with actual duration
1846
+ trackActivity('detailed_view', {
1847
+ property: currentPropertyInModal,
1848
+ duration: durationSeconds
1849
+ });
1850
+
1851
+ // Track modal close
1852
+ trackActivity('property_details_modal_close', {
1853
+ property_id: currentPropertyInModal.id,
1854
+ property_name: currentPropertyInModal.propertyName,
1855
+ duration_seconds: durationSeconds,
1856
+ timestamp: new Date().toISOString()
1857
+ });
1858
+
1859
+ console.log(`Property details viewed: ${currentPropertyInModal.propertyName} for ${durationSeconds} seconds`);
1860
+
1861
+ propertyModalOpenTime = null;
1862
+ currentPropertyInModal = null;
1863
+ }
1864
+ }, { once: true }); // Use once: true to prevent multiple event listeners
1865
+ }
1866
+
1867
+ function openVisitModalFromDetails() {
1868
+ if (currentPropertyInModal) {
1869
+ // Close property details modal
1870
+ const propertyModal = bootstrap.Modal.getInstance(document.getElementById('propertyDetailsModal'));
1871
+ propertyModal.hide();
1872
+
1873
+ // Open visit modal with current property
1874
+ openVisitModal(
1875
+ currentPropertyInModal.id,
1876
+ currentPropertyInModal.propertyName,
1877
+ currentPropertyInModal.marketValue,
1878
+ currentPropertyInModal.typeName,
1879
+ currentPropertyInModal.address
1880
+ );
1881
+ }
1882
+ }
1883
+
1884
+ function addToFavorites() {
1885
+ if (currentPropertyInModal) {
1886
+ // Add to favorites logic
1887
+ const favorites = JSON.parse(localStorage.getItem('favorites') || '[]');
1888
+ const existingIndex = favorites.findIndex(f => f.id === currentPropertyInModal.id);
1889
+
1890
+ if (existingIndex === -1) {
1891
+ favorites.push(currentPropertyInModal);
1892
+ localStorage.setItem('favorites', JSON.stringify(favorites));
1893
+ alert('Added to favorites!');
1894
+
1895
+ // Track favorite action
1896
+ trackActivity('property_added_to_favorites', {
1897
+ property_id: currentPropertyInModal.id,
1898
+ property_name: currentPropertyInModal.propertyName
1899
+ });
1900
+ } else {
1901
+ alert('Already in favorites!');
1902
+ }
1903
+ }
1904
+ }
1905
+
1906
+ function shareProperty() {
1907
+ if (currentPropertyInModal) {
1908
+ // Share property logic
1909
+ const shareText = `Check out this property: ${currentPropertyInModal.propertyName} - β‚Ή${parseInt(currentPropertyInModal.marketValue || 0).toLocaleString()}`;
1910
+
1911
+ if (navigator.share) {
1912
+ navigator.share({
1913
+ title: currentPropertyInModal.propertyName,
1914
+ text: shareText,
1915
+ url: window.location.href
1916
+ });
1917
+ } else {
1918
+ // Fallback: copy to clipboard
1919
+ navigator.clipboard.writeText(shareText).then(() => {
1920
+ alert('Property link copied to clipboard!');
1921
+ });
1922
+ }
1923
+
1924
+ // Track share action
1925
+ trackActivity('property_shared', {
1926
+ property_id: currentPropertyInModal.id,
1927
+ property_name: currentPropertyInModal.propertyName
1928
+ });
1929
+ }
1930
+ }
1931
+
1932
+ // Track scroll depth with enhanced analytics
1933
+ let maxScrollDepth = 0;
1934
+ let scrollStartTime = Date.now();
1935
+
1936
+ window.addEventListener('scroll', () => {
1937
+ const scrollDepth = Math.round((window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100);
1938
+ if (scrollDepth > maxScrollDepth) {
1939
+ maxScrollDepth = scrollDepth;
1940
+
1941
+ // Track scroll depth every 10%
1942
+ if (scrollDepth % 10 === 0) {
1943
+ trackActivity('scroll_depth', {
1944
+ depth: scrollDepth,
1945
+ max_depth: maxScrollDepth,
1946
+ time_spent: Math.round((Date.now() - scrollStartTime) / 1000)
1947
+ });
1948
+ }
1949
+ }
1950
+ });
1951
+
1952
+ // Track time spent with enhanced analytics
1953
+ let startTime = Date.now();
1954
+ let lastTrackedTime = 0;
1955
+
1956
+ setInterval(() => {
1957
+ const timeSpent = Math.round((Date.now() - startTime) / 1000);
1958
+
1959
+ // Track every minute
1960
+ if (timeSpent >= lastTrackedTime + 60) {
1961
+ trackActivity('time_spent', {
1962
+ duration: timeSpent,
1963
+ session_duration: timeSpent
1964
+ });
1965
+ lastTrackedTime = timeSpent;
1966
+ }
1967
+ }, 60000);
1968
+
1969
+ // Track page load and session start
1970
+ document.addEventListener('DOMContentLoaded', () => {
1971
+ trackActivity('page_load', {
1972
+ page: 'home',
1973
+ timestamp: new Date().toISOString()
1974
+ });
1975
+ });
1976
+
1977
+ // Track search functionality
1978
+ function performSearch() {
1979
+ const searchQuery = document.getElementById('searchInput').value;
1980
+ const propertyType = document.getElementById('propertyTypeFilter').value;
1981
+ const minPrice = document.getElementById('minPriceFilter').value;
1982
+ const maxPrice = document.getElementById('maxPriceFilter').value;
1983
+
1984
+ const filters = {
1985
+ propertyType: propertyType,
1986
+ minPrice: minPrice,
1987
+ maxPrice: maxPrice
1988
+ };
1989
+
1990
+ // Track search activity
1991
+ trackSearch(searchQuery, filters);
1992
+
1993
+ // Perform search
1994
+ filterProperties();
1995
+ }
1996
+
1997
+ // Enhanced email form submission with tracking
1998
+ document.getElementById('emailForm').addEventListener('submit', async function(e) {
1999
+ e.preventDefault();
2000
+
2001
+ const email = document.getElementById('userEmail').value;
2002
+ userEmail = email;
2003
+
2004
+ // Track email submission
2005
+ trackActivity('email_submit', {
2006
+ email: email,
2007
+ timestamp: new Date().toISOString()
2008
+ });
2009
+
2010
+ try {
2011
+ const userTrackingData = getUserTrackingData();
2012
+ const sessionId = localStorage.getItem('userSessionId');
2013
+
2014
+ const response = await fetch('/api/send-recommendations', {
2015
+ method: 'POST',
2016
+ headers: {
2017
+ 'Content-Type': 'application/json',
2018
+ },
2019
+ body: JSON.stringify({
2020
+ email: email,
2021
+ session_id: sessionId,
2022
+ user_tracking_data: userTrackingData
2023
+ })
2024
+ });
2025
+
2026
+ const data = await response.json();
2027
+
2028
+ if (data.success) {
2029
+ alert('Recommendations sent to your email!');
2030
+ hideFloatingEmail();
2031
+
2032
+ // Track successful email send
2033
+ trackActivity('email_sent_success', {
2034
+ email: email,
2035
+ recommendations_count: data.recommendations?.length || 0
2036
+ });
2037
+
2038
+ // Load recommendations
2039
+ loadRecommendations();
2040
+ } else {
2041
+ alert('Failed to send recommendations. Please try again.');
2042
+
2043
+ // Track failed email send
2044
+ trackActivity('email_sent_failed', {
2045
+ email: email,
2046
+ error: data.error
2047
+ });
2048
+ }
2049
+
2050
+ } catch (error) {
2051
+ console.error('Error sending recommendations:', error);
2052
+ alert('Failed to send recommendations. Please try again.');
2053
+
2054
+ // Track error
2055
+ trackActivity('email_sent_error', {
2056
+ email: email,
2057
+ error: error.message
2058
+ });
2059
+ }
2060
+ });
2061
+
2062
+ // Enhanced recommendations loading with tracking
2063
+ async function loadRecommendations() {
2064
+ try {
2065
+ const sessionId = localStorage.getItem('userSessionId');
2066
+ const userTrackingData = getUserTrackingData();
2067
+
2068
+ const params = new URLSearchParams({
2069
+ email: userEmail,
2070
+ session_id: sessionId
2071
+ });
2072
+
2073
+ const response = await fetch(`/api/recommendations?${params}`, {
2074
+ method: 'POST',
2075
+ headers: {
2076
+ 'Content-Type': 'application/json',
2077
+ },
2078
+ body: JSON.stringify({
2079
+ user_tracking_data: userTrackingData
2080
+ })
2081
+ });
2082
+
2083
+ const data = await response.json();
2084
+
2085
+ if (data.success && data.recommendations) {
2086
+ renderRecommendations(data.recommendations);
2087
+ updateStats(data.user_preferences);
2088
+
2089
+ // Track recommendations loaded
2090
+ trackActivity('recommendations_loaded', {
2091
+ count: data.recommendations.length,
2092
+ user_preferences: data.user_preferences
2093
+ });
2094
+ }
2095
+
2096
+ } catch (error) {
2097
+ console.error('Error loading recommendations:', error);
2098
+
2099
+ // Track error
2100
+ trackActivity('recommendations_error', {
2101
+ error: error.message
2102
+ });
2103
+ }
2104
+ }
2105
+
2106
+ // Enhanced stats update with tracking data
2107
+ function updateStats(preferences) {
2108
+ const userTrackingData = getUserTrackingData();
2109
+
2110
+ document.getElementById('propertiesViewed').textContent = preferences.total_properties_viewed || 0;
2111
+ document.getElementById('timeSpent').textContent = Math.round((preferences.total_time_spent || 0) / 60);
2112
+
2113
+ // Update visit requests count
2114
+ const visitHistory = getVisitHistory();
2115
+ document.getElementById('visitRequests').textContent = visitHistory.length;
2116
+
2117
+ // Get lead score with tracking
2118
+ if (userEmail) {
2119
+ fetch(`/api/lead-qualification?email=${userEmail}`)
2120
+ .then(response => response.json())
2121
+ .then(data => {
2122
+ document.getElementById('leadScore').textContent = data.lead_score || 0;
2123
+
2124
+ // Track lead score update
2125
+ trackActivity('lead_score_updated', {
2126
+ score: data.lead_score,
2127
+ status: data.lead_status
2128
+ });
2129
+ })
2130
+ .catch(error => {
2131
+ console.error('Error getting lead score:', error);
2132
+ });
2133
+ }
2134
+
2135
+ // Track stats update
2136
+ trackActivity('stats_updated', {
2137
+ properties_viewed: preferences.total_properties_viewed,
2138
+ time_spent: preferences.total_time_spent,
2139
+ visit_requests: visitHistory.length
2140
+ });
2141
+ }
2142
+
2143
+ function updateVisitStats(history, attempts) {
2144
+ // Update visit requests count
2145
+ document.getElementById('visitRequests').textContent = history.length;
2146
+
2147
+ // Calculate average modal duration
2148
+ const interactions = getModalInteractions();
2149
+ const avgDuration = interactions.length > 0
2150
+ ? Math.round(interactions.reduce((sum, i) => sum + i.duration, 0) / interactions.length / 1000)
2151
+ : 0;
2152
+
2153
+ // Track visit analytics
2154
+ trackActivity('visit_analytics_updated', {
2155
+ total_requests: history.length,
2156
+ total_attempts: attempts.length,
2157
+ total_interactions: interactions.length,
2158
+ average_modal_duration: avgDuration
2159
+ });
2160
+
2161
+ // Show visit analytics in console for debugging
2162
+ console.log('Visit Analytics:', {
2163
+ totalRequests: history.length,
2164
+ totalAttempts: attempts.length,
2165
+ totalInteractions: interactions.length,
2166
+ averageModalDuration: avgDuration + ' seconds'
2167
+ });
2168
+ }
2169
+
2170
+ // Add automatic detailed view tracking for property cards
2171
+ function addDetailedViewTracking() {
2172
+ const propertyCards = document.querySelectorAll('.property-card');
2173
+ propertyCards.forEach(card => {
2174
+ let viewStartTime = null;
2175
+
2176
+ card.addEventListener('mouseenter', () => {
2177
+ viewStartTime = Date.now();
2178
+ });
2179
+
2180
+ card.addEventListener('mouseleave', () => {
2181
+ if (viewStartTime) {
2182
+ const duration = Math.round((Date.now() - viewStartTime) / 1000);
2183
+ if (duration >= 2) { // Track if viewed for at least 2 seconds
2184
+ const propertyId = card.getAttribute('data-property-id');
2185
+ const property = allProperties.find(p => p.id == propertyId);
2186
+ if (property) {
2187
+ trackDetailedView(property, duration);
2188
+ console.log(`Auto-tracked detailed view: ${property.propertyName} for ${duration}s`);
2189
+ }
2190
+ }
2191
+ viewStartTime = null;
2192
+ }
2193
+ });
2194
+ });
2195
+ }
2196
+
2197
+ // Call this function after properties are loaded
2198
+ function initializeDetailedViewTracking() {
2199
+ setTimeout(() => {
2200
+ addDetailedViewTracking();
2201
+ }, 1000); // Wait for properties to load
2202
+ }
2203
+
2204
+ // Test functions for debugging
2205
+ function testPropertyClick() {
2206
+ console.log('πŸ§ͺ Testing property click...');
2207
+
2208
+ // Create a test property
2209
+ const testProperty = {
2210
+ id: 999,
2211
+ propertyName: 'Test Property',
2212
+ typeName: 'Villa',
2213
+ marketValue: 5000000,
2214
+ beds: 3,
2215
+ baths: 2,
2216
+ totalSquareFeet: 2000,
2217
+ numberOfRooms: 4,
2218
+ address: 'Test Address, Test City',
2219
+ description: 'This is a test property for debugging purposes.',
2220
+ propertyImages: [],
2221
+ features: ['parking', 'garden', 'security']
2222
+ };
2223
+
2224
+ console.log('πŸ§ͺ Test property created:', testProperty);
2225
+
2226
+ // Simulate property click
2227
+ viewProperty(testProperty);
2228
+
2229
+ console.log('πŸ§ͺ Test property click completed');
2230
+ }
2231
+
2232
+ function testModal() {
2233
+ console.log('πŸ§ͺ Testing modal...');
2234
+ const testProperty = {
2235
+ id: 888,
2236
+ propertyName: 'Test Modal Property',
2237
+ typeName: 'Apartment',
2238
+ marketValue: 3000000,
2239
+ beds: 2,
2240
+ baths: 2,
2241
+ totalSquareFeet: 1200,
2242
+ numberOfRooms: 3,
2243
+ address: 'Test Modal Address',
2244
+ description: 'This is a test modal property.',
2245
+ propertyImages: [],
2246
+ features: ['lift', 'security', 'parking']
2247
+ };
2248
+
2249
+ showPropertyDetailsModal(testProperty);
2250
+ }
2251
+
2252
+ function showTrackingData() {
2253
+ const userTrackingData = getUserTrackingData();
2254
+ console.log('πŸ“Š Current Tracking Data:');
2255
+ console.log('Clicked Properties:', userTrackingData.clickedProperties);
2256
+ console.log('Detailed Views:', userTrackingData.detailedViews);
2257
+ console.log('Property Types:', userTrackingData.propertyTypes);
2258
+ console.log('Price Ranges:', userTrackingData.priceRanges);
2259
+ console.log('Full Data:', userTrackingData);
2260
+
2261
+ showNotification(`πŸ“Š Clicked: ${userTrackingData.clickedProperties?.length || 0}, Views: ${userTrackingData.detailedViews?.length || 0}`, 'info');
2262
+ }
2263
+
2264
+ function clearTrackingData() {
2265
+ localStorage.removeItem('userTrackingData');
2266
+ localStorage.removeItem('userSessionId');
2267
+ console.log('βœ… Tracking data cleared');
2268
+ showNotification('βœ… Tracking data cleared', 'success');
2269
+ }
2270
+ </script>
2271
+ </body>
2272
+ </html>
templates/templates/lead_analysis.html ADDED
@@ -0,0 +1,796 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Lead Analysis Dashboard</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/date-fns@2.29.3/index.min.js"></script>
9
+ <style>
10
+ * {
11
+ margin: 0;
12
+ padding: 0;
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ body {
17
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
18
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
19
+ min-height: 100vh;
20
+ color: #333;
21
+ }
22
+
23
+ .container {
24
+ max-width: 1400px;
25
+ margin: 0 auto;
26
+ padding: 20px;
27
+ }
28
+
29
+ .header {
30
+ text-align: center;
31
+ color: white;
32
+ margin-bottom: 30px;
33
+ }
34
+
35
+ .header h1 {
36
+ font-size: 2.5em;
37
+ margin-bottom: 10px;
38
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
39
+ }
40
+
41
+ .header p {
42
+ font-size: 1.1em;
43
+ opacity: 0.9;
44
+ }
45
+
46
+ .search-section {
47
+ background: white;
48
+ border-radius: 15px;
49
+ padding: 30px;
50
+ margin-bottom: 30px;
51
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
52
+ }
53
+
54
+ .search-form {
55
+ display: flex;
56
+ gap: 15px;
57
+ align-items: end;
58
+ justify-content: center;
59
+ flex-wrap: wrap;
60
+ }
61
+
62
+ .input-group {
63
+ display: flex;
64
+ flex-direction: column;
65
+ min-width: 200px;
66
+ }
67
+
68
+ .input-group label {
69
+ font-weight: 600;
70
+ margin-bottom: 5px;
71
+ color: #555;
72
+ }
73
+
74
+ .input-group input {
75
+ padding: 12px 15px;
76
+ border: 2px solid #e0e0e0;
77
+ border-radius: 8px;
78
+ font-size: 16px;
79
+ transition: all 0.3s ease;
80
+ }
81
+
82
+ .input-group input:focus {
83
+ outline: none;
84
+ border-color: #667eea;
85
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
86
+ }
87
+
88
+ .btn {
89
+ padding: 12px 25px;
90
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
91
+ color: white;
92
+ border: none;
93
+ border-radius: 8px;
94
+ font-size: 16px;
95
+ font-weight: 600;
96
+ cursor: pointer;
97
+ transition: all 0.3s ease;
98
+ }
99
+
100
+ .btn:hover {
101
+ transform: translateY(-2px);
102
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
103
+ }
104
+
105
+ .btn:disabled {
106
+ opacity: 0.6;
107
+ cursor: not-allowed;
108
+ transform: none;
109
+ }
110
+
111
+ .loading {
112
+ text-align: center;
113
+ padding: 40px;
114
+ color: white;
115
+ font-size: 18px;
116
+ }
117
+
118
+ .error {
119
+ background: #ff6b6b;
120
+ color: white;
121
+ padding: 20px;
122
+ border-radius: 10px;
123
+ margin: 20px 0;
124
+ text-align: center;
125
+ }
126
+
127
+ .results-section {
128
+ display: none;
129
+ }
130
+
131
+ .lead-status {
132
+ background: white;
133
+ border-radius: 15px;
134
+ padding: 25px;
135
+ margin-bottom: 20px;
136
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
137
+ }
138
+
139
+ .status-header {
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: space-between;
143
+ margin-bottom: 20px;
144
+ flex-wrap: wrap;
145
+ gap: 15px;
146
+ }
147
+
148
+ .status-badge {
149
+ padding: 10px 20px;
150
+ border-radius: 25px;
151
+ font-weight: bold;
152
+ font-size: 16px;
153
+ text-transform: uppercase;
154
+ letter-spacing: 1px;
155
+ }
156
+
157
+ .lead-score {
158
+ text-align: center;
159
+ }
160
+
161
+ .score-circle {
162
+ width: 120px;
163
+ height: 120px;
164
+ border-radius: 50%;
165
+ display: flex;
166
+ align-items: center;
167
+ justify-content: center;
168
+ margin: 0 auto 10px;
169
+ font-size: 24px;
170
+ font-weight: bold;
171
+ color: white;
172
+ background: conic-gradient(#667eea 0deg, #764ba2 360deg);
173
+ }
174
+
175
+ .analytics-grid {
176
+ display: grid;
177
+ grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
178
+ gap: 20px;
179
+ margin-bottom: 20px;
180
+ }
181
+
182
+ .analytics-card {
183
+ background: white;
184
+ border-radius: 15px;
185
+ padding: 25px;
186
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
187
+ }
188
+
189
+ .card-header {
190
+ display: flex;
191
+ align-items: center;
192
+ margin-bottom: 20px;
193
+ }
194
+
195
+ .card-icon {
196
+ width: 40px;
197
+ height: 40px;
198
+ border-radius: 10px;
199
+ display: flex;
200
+ align-items: center;
201
+ justify-content: center;
202
+ margin-right: 15px;
203
+ font-size: 20px;
204
+ color: white;
205
+ }
206
+
207
+ .card-title {
208
+ font-size: 18px;
209
+ font-weight: 600;
210
+ color: #333;
211
+ }
212
+
213
+ .metric-item {
214
+ display: flex;
215
+ justify-content: space-between;
216
+ align-items: center;
217
+ padding: 10px 0;
218
+ border-bottom: 1px solid #f0f0f0;
219
+ }
220
+
221
+ .metric-item:last-child {
222
+ border-bottom: none;
223
+ }
224
+
225
+ .metric-label {
226
+ color: #666;
227
+ font-weight: 500;
228
+ }
229
+
230
+ .metric-value {
231
+ font-weight: 600;
232
+ color: #333;
233
+ }
234
+
235
+ .properties-section {
236
+ background: white;
237
+ border-radius: 15px;
238
+ padding: 25px;
239
+ margin-bottom: 20px;
240
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
241
+ }
242
+
243
+ .properties-grid {
244
+ display: grid;
245
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
246
+ gap: 15px;
247
+ margin-top: 20px;
248
+ }
249
+
250
+ .property-card {
251
+ border: 2px solid #f0f0f0;
252
+ border-radius: 10px;
253
+ padding: 20px;
254
+ transition: all 0.3s ease;
255
+ }
256
+
257
+ .property-card:hover {
258
+ border-color: #667eea;
259
+ transform: translateY(-2px);
260
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
261
+ }
262
+
263
+ .property-name {
264
+ font-weight: 600;
265
+ color: #333;
266
+ margin-bottom: 10px;
267
+ }
268
+
269
+ .property-price {
270
+ font-size: 18px;
271
+ font-weight: bold;
272
+ color: #667eea;
273
+ margin-bottom: 10px;
274
+ }
275
+
276
+ .property-stats {
277
+ display: flex;
278
+ justify-content: space-between;
279
+ font-size: 14px;
280
+ color: #666;
281
+ }
282
+
283
+ .recommendations {
284
+ background: white;
285
+ border-radius: 15px;
286
+ padding: 25px;
287
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
288
+ }
289
+
290
+ .recommendation-item {
291
+ background: #f8f9ff;
292
+ border-left: 4px solid #667eea;
293
+ padding: 15px;
294
+ margin-bottom: 10px;
295
+ border-radius: 5px;
296
+ }
297
+
298
+ .chart-container {
299
+ position: relative;
300
+ height: 300px;
301
+ margin-top: 20px;
302
+ }
303
+
304
+ @media (max-width: 768px) {
305
+ .search-form {
306
+ flex-direction: column;
307
+ align-items: stretch;
308
+ }
309
+
310
+ .input-group {
311
+ min-width: auto;
312
+ }
313
+
314
+ .status-header {
315
+ flex-direction: column;
316
+ text-align: center;
317
+ }
318
+
319
+ .analytics-grid {
320
+ grid-template-columns: 1fr;
321
+ }
322
+ }
323
+
324
+ .fade-in {
325
+ animation: fadeIn 0.6s ease-in-out;
326
+ }
327
+
328
+ @keyframes fadeIn {
329
+ from { opacity: 0; transform: translateY(20px); }
330
+ to { opacity: 1; transform: translateY(0); }
331
+ }
332
+ </style>
333
+ </head>
334
+ <body>
335
+ <div class="container">
336
+ <div class="header">
337
+ <h1>🎯 Lead Analysis Dashboard</h1>
338
+ <p>Comprehensive Customer Behavior Analytics & Lead Qualification</p>
339
+ </div>
340
+
341
+ <div class="search-section">
342
+ <form class="search-form" id="searchForm">
343
+ <div class="input-group">
344
+ <label for="customerId">Customer ID</label>
345
+ <input type="number" id="customerId" name="customerId" placeholder="Enter Customer ID" required
346
+ value="{{ customer_id if customer_id else '' }}">
347
+ </div>
348
+ <button type="submit" class="btn" id="searchBtn">
349
+ πŸ” Analyze Customer
350
+ </button>
351
+ </form>
352
+ </div>
353
+
354
+ <div id="loadingSection" class="loading" style="display: none;">
355
+ <div>πŸ”„ Analyzing customer data...</div>
356
+ <div style="margin-top: 10px; font-size: 14px; opacity: 0.8;">
357
+ Fetching lead qualification and property analytics
358
+ </div>
359
+ </div>
360
+
361
+ <div id="errorSection" class="error" style="display: none;"></div>
362
+
363
+ <div id="resultsSection" class="results-section">
364
+ <!-- Lead Status Section -->
365
+ <div class="lead-status fade-in">
366
+ <div class="status-header">
367
+ <div>
368
+ <h2>Lead Qualification Status</h2>
369
+ <div id="statusBadge" class="status-badge"></div>
370
+ <p id="statusDescription" style="margin-top: 10px; color: #666;"></p>
371
+ </div>
372
+ <div class="lead-score">
373
+ <div id="scoreCircle" class="score-circle"></div>
374
+ <div>Lead Score</div>
375
+ </div>
376
+ </div>
377
+ </div>
378
+
379
+ <!-- Analytics Grid -->
380
+ <div class="analytics-grid fade-in">
381
+ <!-- Engagement Analytics -->
382
+ <div class="analytics-card">
383
+ <div class="card-header">
384
+ <div class="card-icon" style="background: #667eea;">πŸ“Š</div>
385
+ <div class="card-title">Engagement Analytics</div>
386
+ </div>
387
+ <div id="engagementMetrics"></div>
388
+ <div class="chart-container">
389
+ <canvas id="engagementChart"></canvas>
390
+ </div>
391
+ </div>
392
+
393
+ <!-- Property Preferences -->
394
+ <div class="analytics-card">
395
+ <div class="card-header">
396
+ <div class="card-icon" style="background: #764ba2;">🏠</div>
397
+ <div class="card-title">Property Preferences</div>
398
+ </div>
399
+ <div id="propertyPreferences"></div>
400
+ <div class="chart-container">
401
+ <canvas id="propertyChart"></canvas>
402
+ </div>
403
+ </div>
404
+
405
+ <!-- Price Analysis -->
406
+ <div class="analytics-card">
407
+ <div class="card-header">
408
+ <div class="card-icon" style="background: #f39c12;">πŸ’°</div>
409
+ <div class="card-title">Price Analysis</div>
410
+ </div>
411
+ <div id="priceAnalysis"></div>
412
+ </div>
413
+
414
+ <!-- Conversion Probability -->
415
+ <div class="analytics-card">
416
+ <div class="card-header">
417
+ <div class="card-icon" style="background: #e74c3c;">🎯</div>
418
+ <div class="card-title">Conversion Probability</div>
419
+ </div>
420
+ <div id="conversionAnalysis"></div>
421
+ <div class="chart-container">
422
+ <canvas id="conversionChart"></canvas>
423
+ </div>
424
+ </div>
425
+ </div>
426
+
427
+ <!-- Properties Section -->
428
+ <div class="properties-section fade-in">
429
+ <div class="card-header">
430
+ <div class="card-icon" style="background: #2ecc71;">🏘️</div>
431
+ <div class="card-title">Viewed Properties</div>
432
+ </div>
433
+ <div id="propertiesGrid" class="properties-grid"></div>
434
+ </div>
435
+
436
+ <!-- Recommendations Section -->
437
+ <div class="recommendations fade-in">
438
+ <div class="card-header">
439
+ <div class="card-icon" style="background: #9b59b6;">πŸ’‘</div>
440
+ <div class="card-title">AI Recommendations</div>
441
+ </div>
442
+ <div id="recommendationsContent"></div>
443
+ </div>
444
+ </div>
445
+ </div>
446
+
447
+ <script>
448
+ // Global variables
449
+ let currentData = null;
450
+ let charts = {};
451
+
452
+ // Initialize
453
+ document.addEventListener('DOMContentLoaded', function() {
454
+ const customerId = document.getElementById('customerId').value;
455
+ if (customerId) {
456
+ fetchAnalysis(customerId);
457
+ }
458
+ });
459
+
460
+ // Form submission
461
+ document.getElementById('searchForm').addEventListener('submit', function(e) {
462
+ e.preventDefault();
463
+ const customerId = document.getElementById('customerId').value;
464
+ if (customerId) {
465
+ fetchAnalysis(customerId);
466
+ }
467
+ });
468
+
469
+ // Fetch analysis data
470
+ async function fetchAnalysis(customerId) {
471
+ showLoading();
472
+ hideError();
473
+ hideResults();
474
+
475
+ try {
476
+ const response = await fetch(`/api/customer/${customerId}`);
477
+ const data = await response.json();
478
+
479
+ if (data.success) {
480
+ currentData = data;
481
+ displayResults(data);
482
+ showResults();
483
+ } else {
484
+ showError(data.error || 'Failed to fetch analysis');
485
+ }
486
+ } catch (error) {
487
+ showError(`Error: ${error.message}`);
488
+ } finally {
489
+ hideLoading();
490
+ }
491
+ }
492
+
493
+ // Display results
494
+ function displayResults(data) {
495
+ const leadData = data.data.lead_qualification;
496
+ const analytics = data.data.analytics;
497
+ const properties = data.data.properties;
498
+
499
+ // Update lead status
500
+ updateLeadStatus(leadData);
501
+
502
+ // Update analytics cards
503
+ updateEngagementMetrics(analytics);
504
+ updatePropertyPreferences(analytics);
505
+ updatePriceAnalysis(analytics);
506
+ updateConversionAnalysis(analytics);
507
+
508
+ // Update properties
509
+ updatePropertiesGrid(properties);
510
+
511
+ // Update recommendations
512
+ updateRecommendations(analytics.recommendations);
513
+
514
+ // Create charts
515
+ createCharts(analytics);
516
+ }
517
+
518
+ // Update lead status
519
+ function updateLeadStatus(leadData) {
520
+ const statusBadge = document.getElementById('statusBadge');
521
+ const statusDescription = document.getElementById('statusDescription');
522
+ const scoreCircle = document.getElementById('scoreCircle');
523
+
524
+ statusBadge.textContent = leadData.lead_status;
525
+ statusBadge.style.backgroundColor = leadData.status_color;
526
+ statusDescription.textContent = leadData.status_description;
527
+ scoreCircle.textContent = `${Math.round(leadData.lead_score)}/100`;
528
+ }
529
+
530
+ // Update engagement metrics
531
+ function updateEngagementMetrics(analytics) {
532
+ const container = document.getElementById('engagementMetrics');
533
+ container.innerHTML = `
534
+ <div class="metric-item">
535
+ <span class="metric-label">Engagement Level</span>
536
+ <span class="metric-value">${analytics.engagement_level}</span>
537
+ </div>
538
+ <div class="metric-item">
539
+ <span class="metric-label">Total Views</span>
540
+ <span class="metric-value">${currentData.data.summary.total_views}</span>
541
+ </div>
542
+ <div class="metric-item">
543
+ <span class="metric-label">Total Duration</span>
544
+ <span class="metric-value">${formatDuration(currentData.data.summary.total_duration)}</span>
545
+ </div>
546
+ <div class="metric-item">
547
+ <span class="metric-label">Engagement Score</span>
548
+ <span class="metric-value">${Math.round(currentData.data.summary.engagement_score)}</span>
549
+ </div>
550
+ `;
551
+ }
552
+
553
+ // Update property preferences
554
+ function updatePropertyPreferences(analytics) {
555
+ const container = document.getElementById('propertyPreferences');
556
+ const preferredTypes = analytics.preferred_property_types || [];
557
+
558
+ container.innerHTML = preferredTypes.map((type, index) => `
559
+ <div class="metric-item">
560
+ <span class="metric-label">Preference ${index + 1}</span>
561
+ <span class="metric-value">${type}</span>
562
+ </div>
563
+ `).join('');
564
+ }
565
+
566
+ // Update price analysis
567
+ function updatePriceAnalysis(analytics) {
568
+ const container = document.getElementById('priceAnalysis');
569
+ const priceData = analytics.price_preferences || {};
570
+
571
+ container.innerHTML = `
572
+ <div class="metric-item">
573
+ <span class="metric-label">Average Price</span>
574
+ <span class="metric-value">${formatPrice(priceData.avg_price || 0)}</span>
575
+ </div>
576
+ <div class="metric-item">
577
+ <span class="metric-label">Min Price</span>
578
+ <span class="metric-value">${formatPrice(priceData.min_price || 0)}</span>
579
+ </div>
580
+ <div class="metric-item">
581
+ <span class="metric-label">Max Price</span>
582
+ <span class="metric-value">${formatPrice(priceData.max_price || 0)}</span>
583
+ </div>
584
+ <div class="metric-item">
585
+ <span class="metric-label">Price Range</span>
586
+ <span class="metric-value">${formatPrice(priceData.price_range || 0)}</span>
587
+ </div>
588
+ `;
589
+ }
590
+
591
+ // Update conversion analysis
592
+ function updateConversionAnalysis(analytics) {
593
+ const container = document.getElementById('conversionAnalysis');
594
+ const conversionData = analytics.conversion_probability || {};
595
+
596
+ container.innerHTML = `
597
+ <div class="metric-item">
598
+ <span class="metric-label">Conversion Probability</span>
599
+ <span class="metric-value">${Math.round(conversionData.final_probability || 0)}%</span>
600
+ </div>
601
+ <div class="metric-item">
602
+ <span class="metric-label">Confidence Level</span>
603
+ <span class="metric-value">${conversionData.confidence_level || 'Unknown'}</span>
604
+ </div>
605
+ <div class="metric-item">
606
+ <span class="metric-label">Opportunity Score</span>
607
+ <span class="metric-value">${Math.round(analytics.opportunity_score || 0)}</span>
608
+ </div>
609
+ <div class="metric-item">
610
+ <span class="metric-label">Risk Level</span>
611
+ <span class="metric-value">${analytics.risk_assessment?.risk_level || 'Unknown'}</span>
612
+ </div>
613
+ `;
614
+ }
615
+
616
+ // Update properties grid
617
+ function updatePropertiesGrid(properties) {
618
+ const container = document.getElementById('propertiesGrid');
619
+
620
+ container.innerHTML = properties.map(property => `
621
+ <div class="property-card">
622
+ <div class="property-name">${property.propertyName || 'Unknown Property'}</div>
623
+ <div class="property-price">${formatPrice(property.price || 0)}</div>
624
+ <div class="property-stats">
625
+ <span>Views: ${property.viewCount || 0}</span>
626
+ <span>Duration: ${formatDuration(property.totalDuration || 0)}</span>
627
+ <span>Type: ${property.propertyTypeName || 'Unknown'}</span>
628
+ </div>
629
+ </div>
630
+ `).join('');
631
+ }
632
+
633
+ // Update recommendations
634
+ function updateRecommendations(recommendations) {
635
+ const container = document.getElementById('recommendationsContent');
636
+
637
+ if (recommendations && recommendations.length > 0) {
638
+ container.innerHTML = recommendations.map(rec => `
639
+ <div class="recommendation-item">${rec}</div>
640
+ `).join('');
641
+ } else {
642
+ container.innerHTML = '<div class="recommendation-item">No specific recommendations available.</div>';
643
+ }
644
+ }
645
+
646
+ // Create charts
647
+ function createCharts(analytics) {
648
+ createEngagementChart(analytics);
649
+ createPropertyChart(analytics);
650
+ createConversionChart(analytics);
651
+ }
652
+
653
+ // Create engagement chart
654
+ function createEngagementChart(analytics) {
655
+ const ctx = document.getElementById('engagementChart').getContext('2d');
656
+
657
+ if (charts.engagement) {
658
+ charts.engagement.destroy();
659
+ }
660
+
661
+ charts.engagement = new Chart(ctx, {
662
+ type: 'doughnut',
663
+ data: {
664
+ labels: ['High Engagement', 'Medium Engagement', 'Low Engagement'],
665
+ datasets: [{
666
+ data: [40, 35, 25], // Sample data
667
+ backgroundColor: ['#667eea', '#764ba2', '#95a5a6']
668
+ }]
669
+ },
670
+ options: {
671
+ responsive: true,
672
+ maintainAspectRatio: false,
673
+ plugins: {
674
+ legend: {
675
+ position: 'bottom'
676
+ }
677
+ }
678
+ }
679
+ });
680
+ }
681
+
682
+ // Create property chart
683
+ function createPropertyChart(analytics) {
684
+ const ctx = document.getElementById('propertyChart').getContext('2d');
685
+
686
+ if (charts.property) {
687
+ charts.property.destroy();
688
+ }
689
+
690
+ const propertyTypes = analytics.preferred_property_types || [];
691
+ const labels = propertyTypes.slice(0, 5);
692
+ const data = labels.map((_, i) => Math.max(1, 5 - i));
693
+
694
+ charts.property = new Chart(ctx, {
695
+ type: 'bar',
696
+ data: {
697
+ labels: labels,
698
+ datasets: [{
699
+ label: 'Interest Level',
700
+ data: data,
701
+ backgroundColor: '#667eea'
702
+ }]
703
+ },
704
+ options: {
705
+ responsive: true,
706
+ maintainAspectRatio: false,
707
+ plugins: {
708
+ legend: {
709
+ display: false
710
+ }
711
+ },
712
+ scales: {
713
+ y: {
714
+ beginAtZero: true
715
+ }
716
+ }
717
+ }
718
+ });
719
+ }
720
+
721
+ // Create conversion chart
722
+ function createConversionChart(analytics) {
723
+ const ctx = document.getElementById('conversionChart').getContext('2d');
724
+
725
+ if (charts.conversion) {
726
+ charts.conversion.destroy();
727
+ }
728
+
729
+ const conversionProb = analytics.conversion_probability?.final_probability || 0;
730
+
731
+ charts.conversion = new Chart(ctx, {
732
+ type: 'doughnut',
733
+ data: {
734
+ labels: ['Conversion Probability', 'Remaining'],
735
+ datasets: [{
736
+ data: [conversionProb, 100 - conversionProb],
737
+ backgroundColor: ['#e74c3c', '#ecf0f1'],
738
+ borderWidth: 0
739
+ }]
740
+ },
741
+ options: {
742
+ responsive: true,
743
+ maintainAspectRatio: false,
744
+ cutout: '70%',
745
+ plugins: {
746
+ legend: {
747
+ display: false
748
+ }
749
+ }
750
+ }
751
+ });
752
+ }
753
+
754
+ // Utility functions
755
+ function formatPrice(price) {
756
+ return `β‚Ή${price.toLocaleString('en-IN')}`;
757
+ }
758
+
759
+ function formatDuration(seconds) {
760
+ const hours = Math.floor(seconds / 3600);
761
+ const minutes = Math.floor((seconds % 3600) / 60);
762
+ return `${hours}h ${minutes}m`;
763
+ }
764
+
765
+ function showLoading() {
766
+ document.getElementById('loadingSection').style.display = 'block';
767
+ document.getElementById('searchBtn').disabled = true;
768
+ document.getElementById('searchBtn').textContent = 'πŸ”„ Analyzing...';
769
+ }
770
+
771
+ function hideLoading() {
772
+ document.getElementById('loadingSection').style.display = 'none';
773
+ document.getElementById('searchBtn').disabled = false;
774
+ document.getElementById('searchBtn').textContent = 'πŸ” Analyze Customer';
775
+ }
776
+
777
+ function showError(message) {
778
+ const errorSection = document.getElementById('errorSection');
779
+ errorSection.textContent = message;
780
+ errorSection.style.display = 'block';
781
+ }
782
+
783
+ function hideError() {
784
+ document.getElementById('errorSection').style.display = 'none';
785
+ }
786
+
787
+ function showResults() {
788
+ document.getElementById('resultsSection').style.display = 'block';
789
+ }
790
+
791
+ function hideResults() {
792
+ document.getElementById('resultsSection').style.display = 'none';
793
+ }
794
+ </script>
795
+ </body>
796
+ </html>
test_email_system.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test Email System
4
+ Independent testing of email functionality
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import time
10
+ import logging
11
+ import requests
12
+ from datetime import datetime
13
+
14
+ # Configure logging
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ def test_email_automation_service():
19
+ """Test the email automation service"""
20
+ logger.info("πŸ§ͺ Testing Email Automation Service...")
21
+
22
+ base_url = "http://localhost:5001"
23
+
24
+ # Test health check
25
+ try:
26
+ response = requests.get(f"{base_url}/api/email/health", timeout=10)
27
+ if response.status_code == 200:
28
+ logger.info("βœ… Email automation service is running")
29
+ else:
30
+ logger.error(f"❌ Email automation service health check failed: {response.status_code}")
31
+ return False
32
+ except Exception as e:
33
+ logger.error(f"❌ Cannot connect to email automation service: {e}")
34
+ return False
35
+
36
+ # Test email sending
37
+ try:
38
+ response = requests.post(f"{base_url}/api/email/test", timeout=30)
39
+ if response.status_code == 200:
40
+ result = response.json()
41
+ if result.get('success'):
42
+ logger.info("βœ… Test email sent successfully")
43
+ logger.info(f"πŸ“§ Recipient: {result.get('recipient')}")
44
+ logger.info(f"πŸ“§ Subject: {result.get('subject')}")
45
+ return True
46
+ else:
47
+ logger.error(f"❌ Test email failed: {result.get('error')}")
48
+ return False
49
+ else:
50
+ logger.error(f"❌ Test email request failed: {response.status_code}")
51
+ return False
52
+ except Exception as e:
53
+ logger.error(f"❌ Test email request failed: {e}")
54
+ return False
55
+
56
+ def test_automated_email():
57
+ """Test automated email generation"""
58
+ logger.info("πŸ§ͺ Testing Automated Email Generation...")
59
+
60
+ base_url = "http://localhost:5001"
61
+
62
+ # Test automated email for customer 105
63
+ test_data = {
64
+ 'customer_id': 105,
65
+ 'email_type': 'behavioral_trigger',
66
+ 'recipient_email': 'sameermujahid7777@gmail.com'
67
+ }
68
+
69
+ try:
70
+ response = requests.post(f"{base_url}/api/email/automated",
71
+ json=test_data, timeout=60)
72
+ if response.status_code == 200:
73
+ result = response.json()
74
+ if result.get('success'):
75
+ logger.info("βœ… Automated email sent successfully")
76
+ logger.info(f"πŸ“§ Customer ID: {result.get('customer_id')}")
77
+ logger.info(f"πŸ“§ Email Type: {result.get('email_type')}")
78
+ logger.info(f"πŸ“§ Subject: {result.get('subject')}")
79
+ return True
80
+ else:
81
+ logger.error(f"❌ Automated email failed: {result.get('error')}")
82
+ return False
83
+ else:
84
+ logger.error(f"❌ Automated email request failed: {response.status_code}")
85
+ return False
86
+ except Exception as e:
87
+ logger.error(f"❌ Automated email request failed: {e}")
88
+ return False
89
+
90
+ def test_api_service():
91
+ """Test the main API service"""
92
+ logger.info("πŸ§ͺ Testing Main API Service...")
93
+
94
+ base_url = "http://localhost:5000"
95
+
96
+ # Test health check
97
+ try:
98
+ response = requests.get(f"{base_url}/api/health", timeout=10)
99
+ if response.status_code == 200:
100
+ logger.info("βœ… Main API service is running")
101
+ return True
102
+ else:
103
+ logger.error(f"❌ Main API service health check failed: {response.status_code}")
104
+ return False
105
+ except Exception as e:
106
+ logger.error(f"❌ Cannot connect to main API service: {e}")
107
+ return False
108
+
109
+ def test_customer_analysis():
110
+ """Test customer analysis functionality"""
111
+ logger.info("πŸ§ͺ Testing Customer Analysis...")
112
+
113
+ base_url = "http://localhost:5000"
114
+
115
+ # Test customer 105 analysis
116
+ try:
117
+ response = requests.get(f"{base_url}/api/customer/105", timeout=30)
118
+ if response.status_code == 200:
119
+ result = response.json()
120
+ if result.get('success'):
121
+ logger.info("βœ… Customer analysis successful")
122
+ lead_data = result.get('data', {}).get('lead_qualification', {})
123
+ logger.info(f"πŸ“Š Lead Status: {lead_data.get('lead_status')}")
124
+ logger.info(f"πŸ“Š Lead Score: {lead_data.get('lead_score')}")
125
+ return True
126
+ else:
127
+ logger.error(f"❌ Customer analysis failed: {result.get('error')}")
128
+ return False
129
+ else:
130
+ logger.error(f"❌ Customer analysis request failed: {response.status_code}")
131
+ return False
132
+ except Exception as e:
133
+ logger.error(f"❌ Customer analysis request failed: {e}")
134
+ return False
135
+
136
+ def test_ai_analysis():
137
+ """Test AI analysis functionality"""
138
+ logger.info("πŸ§ͺ Testing AI Analysis...")
139
+
140
+ base_url = "http://localhost:5000"
141
+
142
+ # Test AI analysis for customer 105
143
+ try:
144
+ response = requests.get(f"{base_url}/api/ai-customer/105", timeout=60)
145
+ if response.status_code == 200:
146
+ result = response.json()
147
+ if result.get('success'):
148
+ logger.info("βœ… AI analysis successful")
149
+ ai_insights = result.get('data', {}).get('ai_insights', {})
150
+ logger.info(f"🧠 Personality Type: {ai_insights.get('ai_personality_type')}")
151
+ logger.info(f"🧠 Buying Motivation: {ai_insights.get('buying_motivation')}")
152
+ return True
153
+ else:
154
+ logger.error(f"❌ AI analysis failed: {result.get('error')}")
155
+ return False
156
+ else:
157
+ logger.error(f"❌ AI analysis request failed: {response.status_code}")
158
+ return False
159
+ except Exception as e:
160
+ logger.error(f"❌ AI analysis request failed: {e}")
161
+ return False
162
+
163
+ def main():
164
+ """Main test function"""
165
+ logger.info("=" * 80)
166
+ logger.info("πŸ§ͺ AI Lead Analysis System - Email Testing Suite")
167
+ logger.info("=" * 80)
168
+ logger.info(f"πŸ“… Test Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
169
+ logger.info("")
170
+
171
+ # Test results
172
+ tests = {
173
+ 'API Service': test_api_service,
174
+ 'Customer Analysis': test_customer_analysis,
175
+ 'AI Analysis': test_ai_analysis,
176
+ 'Email Automation Service': test_email_automation_service,
177
+ 'Test Email': test_email_automation_service,
178
+ 'Automated Email': test_automated_email
179
+ }
180
+
181
+ results = {}
182
+
183
+ for test_name, test_func in tests.items():
184
+ logger.info(f"πŸ” Running {test_name} test...")
185
+ try:
186
+ result = test_func()
187
+ results[test_name] = result
188
+ if result:
189
+ logger.info(f"βœ… {test_name} test PASSED")
190
+ else:
191
+ logger.error(f"❌ {test_name} test FAILED")
192
+ except Exception as e:
193
+ logger.error(f"❌ {test_name} test ERROR: {e}")
194
+ results[test_name] = False
195
+
196
+ logger.info("")
197
+
198
+ # Summary
199
+ logger.info("=" * 80)
200
+ logger.info("πŸ“Š TEST SUMMARY")
201
+ logger.info("=" * 80)
202
+
203
+ passed = sum(1 for result in results.values() if result)
204
+ total = len(results)
205
+
206
+ for test_name, result in results.items():
207
+ status = "βœ… PASSED" if result else "❌ FAILED"
208
+ logger.info(f"{test_name}: {status}")
209
+
210
+ logger.info("")
211
+ logger.info(f"πŸ“ˆ Overall Result: {passed}/{total} tests passed")
212
+
213
+ if passed == total:
214
+ logger.info("πŸŽ‰ All tests passed! Email system is working correctly.")
215
+ else:
216
+ logger.warning("⚠️ Some tests failed. Please check the logs above.")
217
+
218
+ logger.info("=" * 80)
219
+
220
+ return passed == total
221
+
222
+ if __name__ == "__main__":
223
+ try:
224
+ success = main()
225
+ sys.exit(0 if success else 1)
226
+ except KeyboardInterrupt:
227
+ logger.info("πŸ›‘ Testing interrupted by user")
228
+ sys.exit(1)
229
+ except Exception as e:
230
+ logger.error(f"❌ Testing failed: {e}")
231
+ sys.exit(1)