Spaces:
Sleeping
Sleeping
Upload 19 files
Browse files- Dockerfile +57 -0
- ai_recommendation_engine.py +0 -0
- api_service_enhanced.py +0 -0
- app.py +358 -0
- email_automation_service.py +0 -0
- mock_data_service.py +232 -0
- requirements.txt +19 -0
- templates/ai_lead_analysis.html +1714 -0
- templates/analytics.html +919 -0
- templates/enhanced_dashboard.html +1264 -0
- templates/index.html +2272 -0
- templates/lead_analysis.html +796 -0
- templates/templates/ai_lead_analysis.html +2152 -0
- templates/templates/analytics.html +919 -0
- templates/templates/enhanced_dashboard.html +1264 -0
- templates/templates/index.html +2272 -0
- templates/templates/lead_analysis.html +796 -0
- test_email_system.py +231 -0
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> </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"> </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)
|