Update destination functionality to get user's location
This commit is contained in:
@@ -1105,7 +1105,7 @@ def qr_destination(qr_url):
|
|||||||
|
|
||||||
@app.route('/qr/<string:qr_url>/checkin', methods=['POST'])
|
@app.route('/qr/<string:qr_url>/checkin', methods=['POST'])
|
||||||
def qr_checkin(qr_url):
|
def qr_checkin(qr_url):
|
||||||
"""Handle staff check-in submission"""
|
"""Handle staff check-in submission with geolocation support"""
|
||||||
try:
|
try:
|
||||||
# Find QR code by URL
|
# Find QR code by URL
|
||||||
qr_code = QRCode.query.filter_by(qr_url=qr_url, active_status=True).first()
|
qr_code = QRCode.query.filter_by(qr_url=qr_url, active_status=True).first()
|
||||||
@@ -1119,13 +1119,21 @@ def qr_checkin(qr_url):
|
|||||||
# Get form data
|
# Get form data
|
||||||
employee_id = request.form.get('employee_id', '').strip()
|
employee_id = request.form.get('employee_id', '').strip()
|
||||||
|
|
||||||
|
# NEW: Get location data from form
|
||||||
|
latitude = request.form.get('latitude', '').strip()
|
||||||
|
longitude = request.form.get('longitude', '').strip()
|
||||||
|
accuracy = request.form.get('accuracy', '').strip()
|
||||||
|
altitude = request.form.get('altitude', '').strip()
|
||||||
|
location_source = request.form.get('location_source', 'manual').strip()
|
||||||
|
address = request.form.get('address', '').strip()
|
||||||
|
|
||||||
if not employee_id:
|
if not employee_id:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': 'Employee ID is required.'
|
'message': 'Employee ID is required.'
|
||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
# Validate employee ID format (adjust regex as needed)
|
# Validate employee ID format
|
||||||
if not re.match(r'^[A-Za-z0-9]{3,20}$', employee_id):
|
if not re.match(r'^[A-Za-z0-9]{3,20}$', employee_id):
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
@@ -1146,10 +1154,34 @@ def qr_checkin(qr_url):
|
|||||||
'message': f'You have already checked in today at {existing_checkin.check_in_time.strftime("%H:%M")}.'
|
'message': f'You have already checked in today at {existing_checkin.check_in_time.strftime("%H:%M")}.'
|
||||||
}), 409
|
}), 409
|
||||||
|
|
||||||
# Get device and location info
|
# Parse user agent
|
||||||
user_agent_string = request.headers.get('User-Agent', '')
|
user_agent = request.headers.get('User-Agent', '')
|
||||||
device_info = detect_device_info(user_agent_string)
|
parsed_agent = parse(user_agent)
|
||||||
ip_address = get_client_ip()
|
device_info = f"{parsed_agent.browser.family} on {parsed_agent.os.family}"
|
||||||
|
|
||||||
|
# Get client IP
|
||||||
|
client_ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.remote_addr)
|
||||||
|
if client_ip and ',' in client_ip:
|
||||||
|
client_ip = client_ip.split(',')[0].strip()
|
||||||
|
|
||||||
|
# NEW: Process location data safely
|
||||||
|
lat_value = None
|
||||||
|
lng_value = None
|
||||||
|
acc_value = None
|
||||||
|
alt_value = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
if latitude and latitude != 'null' and latitude != '':
|
||||||
|
lat_value = float(latitude)
|
||||||
|
if longitude and longitude != 'null' and longitude != '':
|
||||||
|
lng_value = float(longitude)
|
||||||
|
if accuracy and accuracy != 'null' and accuracy != '':
|
||||||
|
acc_value = float(accuracy)
|
||||||
|
if altitude and altitude != 'null' and altitude != '':
|
||||||
|
alt_value = float(altitude)
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
print(f"⚠️ Invalid location data: {e}")
|
||||||
|
# Continue without location data
|
||||||
|
|
||||||
# Create attendance record
|
# Create attendance record
|
||||||
attendance = AttendanceData(
|
attendance = AttendanceData(
|
||||||
@@ -1158,35 +1190,68 @@ def qr_checkin(qr_url):
|
|||||||
check_in_date=today,
|
check_in_date=today,
|
||||||
check_in_time=datetime.now().time(),
|
check_in_time=datetime.now().time(),
|
||||||
device_info=device_info,
|
device_info=device_info,
|
||||||
user_agent=user_agent_string,
|
user_agent=user_agent,
|
||||||
ip_address=ip_address,
|
ip_address=client_ip,
|
||||||
location_name=qr_code.location,
|
location_name=qr_code.location,
|
||||||
status='present'
|
status='present'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# NEW: Add location data if available (safe attribute setting)
|
||||||
|
try:
|
||||||
|
if lat_value is not None:
|
||||||
|
attendance.latitude = lat_value
|
||||||
|
if lng_value is not None:
|
||||||
|
attendance.longitude = lng_value
|
||||||
|
if acc_value is not None:
|
||||||
|
attendance.accuracy = acc_value
|
||||||
|
if alt_value is not None:
|
||||||
|
attendance.altitude = alt_value
|
||||||
|
if location_source:
|
||||||
|
attendance.location_source = location_source
|
||||||
|
if address:
|
||||||
|
attendance.address = address
|
||||||
|
except AttributeError as e:
|
||||||
|
print(f"⚠️ Location columns not available: {e}")
|
||||||
|
# Continue without location data
|
||||||
|
|
||||||
db.session.add(attendance)
|
db.session.add(attendance)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
print(f"Check-in recorded: {employee_id} at {qr_code.name}")
|
# Prepare response
|
||||||
|
response_data = {
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': f'Check-in successful! Welcome to {qr_code.location_event}.',
|
'message': 'Check-in successful!',
|
||||||
'data': {
|
|
||||||
'employee_id': employee_id.upper(),
|
'employee_id': employee_id.upper(),
|
||||||
'location': qr_code.location,
|
'location': qr_code.location,
|
||||||
'event': qr_code.location_event,
|
'event': qr_code.location_event,
|
||||||
'time': datetime.now().strftime('%H:%M'),
|
'time': datetime.now().strftime('%H:%M'),
|
||||||
'date': today.strftime('%B %d, %Y')
|
'date': today.strftime('%Y-%m-%d'),
|
||||||
|
'has_location': bool(lat_value and lng_value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# NEW: Add location info to response
|
||||||
|
if lat_value and lng_value:
|
||||||
|
response_data.update({
|
||||||
|
'location_accuracy': acc_value,
|
||||||
|
'location_address': address
|
||||||
})
|
})
|
||||||
|
|
||||||
|
print(f"✅ Check-in successful: {employee_id.upper()} at {qr_code.location}")
|
||||||
|
if lat_value and lng_value:
|
||||||
|
print(f"📍 Location: {lat_value:.6f}, {lng_value:.6f} (±{acc_value}m) - {location_source}")
|
||||||
|
if address:
|
||||||
|
print(f"🏠 Address: {address}")
|
||||||
|
|
||||||
|
return jsonify(response_data)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
print(f"Error during check-in: {e}")
|
print(f"❌ Check-in error: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': 'An error occurred during check-in. Please try again.'
|
'message': 'System error occurred. Please try again.'
|
||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
@app.route('/qr-codes/<int:qr_id>/toggle-status', methods=['POST'])
|
@app.route('/qr-codes/<int:qr_id>/toggle-status', methods=['POST'])
|
||||||
@@ -1356,6 +1421,81 @@ def attendance_report():
|
|||||||
flash('Error loading attendance report.', 'error')
|
flash('Error loading attendance report.', 'error')
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
|
# 3. ADD NEW ROUTE FOR LOCATION STATISTICS (Optional)
|
||||||
|
@app.route('/admin/location-stats')
|
||||||
|
@admin_required
|
||||||
|
def location_stats():
|
||||||
|
"""View location statistics for admin"""
|
||||||
|
try:
|
||||||
|
# Get basic attendance stats
|
||||||
|
total_checkins = AttendanceData.query.count()
|
||||||
|
|
||||||
|
# Try to get location data (will work only if columns exist)
|
||||||
|
location_checkins = 0
|
||||||
|
recent_locations = []
|
||||||
|
location_accuracy_stats = {
|
||||||
|
'high': 0,
|
||||||
|
'medium': 0,
|
||||||
|
'low': 0,
|
||||||
|
'unknown': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Count check-ins with location data
|
||||||
|
location_checkins = db.session.execute(text("""
|
||||||
|
SELECT COUNT(*) FROM attendance_data
|
||||||
|
WHERE latitude IS NOT NULL AND longitude IS NOT NULL
|
||||||
|
""")).fetchone()[0]
|
||||||
|
|
||||||
|
# Get recent locations
|
||||||
|
recent_locations_result = db.session.execute(text("""
|
||||||
|
SELECT employee_id, latitude, longitude, accuracy, address,
|
||||||
|
check_in_date, check_in_time, location_name, location_source
|
||||||
|
FROM attendance_data
|
||||||
|
WHERE latitude IS NOT NULL
|
||||||
|
ORDER BY created_timestamp DESC
|
||||||
|
LIMIT 20
|
||||||
|
""")).fetchall()
|
||||||
|
|
||||||
|
recent_locations = []
|
||||||
|
for row in recent_locations_result:
|
||||||
|
recent_locations.append({
|
||||||
|
'employee_id': row[0],
|
||||||
|
'latitude': row[1],
|
||||||
|
'longitude': row[2],
|
||||||
|
'accuracy': row[3],
|
||||||
|
'address': row[4],
|
||||||
|
'check_in_date': row[5].strftime('%Y-%m-%d') if row[5] else '',
|
||||||
|
'check_in_time': row[6].strftime('%H:%M') if row[6] else '',
|
||||||
|
'location_name': row[7],
|
||||||
|
'location_source': row[8]
|
||||||
|
})
|
||||||
|
|
||||||
|
# Count accuracy stats
|
||||||
|
accuracy = row[3]
|
||||||
|
if accuracy is None:
|
||||||
|
location_accuracy_stats['unknown'] += 1
|
||||||
|
elif accuracy <= 50:
|
||||||
|
location_accuracy_stats['high'] += 1
|
||||||
|
elif accuracy <= 100:
|
||||||
|
location_accuracy_stats['medium'] += 1
|
||||||
|
else:
|
||||||
|
location_accuracy_stats['low'] += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Location stats query failed: {e}")
|
||||||
|
|
||||||
|
return render_template('location_stats.html',
|
||||||
|
total_checkins=total_checkins,
|
||||||
|
location_checkins=location_checkins,
|
||||||
|
recent_locations=recent_locations,
|
||||||
|
accuracy_stats=location_accuracy_stats)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading location stats: {e}")
|
||||||
|
flash('Error loading location statistics.', 'error')
|
||||||
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
@app.route('/api/attendance/stats')
|
@app.route('/api/attendance/stats')
|
||||||
@admin_required
|
@admin_required
|
||||||
def attendance_stats_api():
|
def attendance_stats_api():
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Simple Database Migration for Geolocation
|
||||||
|
Run this script FIRST before updating your app.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import psycopg2
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Database connection (adjust if needed)
|
||||||
|
DATABASE_URL = os.environ.get('DATABASE_URL', 'postgresql://postgres:postgres1411@localhost/qr_management')
|
||||||
|
|
||||||
|
def add_location_columns():
|
||||||
|
"""Add location columns to attendance_data table"""
|
||||||
|
|
||||||
|
print("🚀 Adding location tracking columns to your database...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Connect to database
|
||||||
|
conn = psycopg2.connect(DATABASE_URL)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
print("✅ Connected to database")
|
||||||
|
|
||||||
|
# Check if attendance_data table exists
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT FROM information_schema.tables
|
||||||
|
WHERE table_name = 'attendance_data'
|
||||||
|
);
|
||||||
|
""")
|
||||||
|
|
||||||
|
if not cursor.fetchone()[0]:
|
||||||
|
print("❌ attendance_data table not found!")
|
||||||
|
print(" Make sure your QR system is set up first")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("✅ attendance_data table found")
|
||||||
|
|
||||||
|
# Add location columns (using IF NOT EXISTS for safety)
|
||||||
|
location_columns = [
|
||||||
|
"ALTER TABLE attendance_data ADD COLUMN IF NOT EXISTS latitude FLOAT",
|
||||||
|
"ALTER TABLE attendance_data ADD COLUMN IF NOT EXISTS longitude FLOAT",
|
||||||
|
"ALTER TABLE attendance_data ADD COLUMN IF NOT EXISTS accuracy FLOAT",
|
||||||
|
"ALTER TABLE attendance_data ADD COLUMN IF NOT EXISTS altitude FLOAT",
|
||||||
|
"ALTER TABLE attendance_data ADD COLUMN IF NOT EXISTS location_source VARCHAR(50) DEFAULT 'manual'",
|
||||||
|
"ALTER TABLE attendance_data ADD COLUMN IF NOT EXISTS address VARCHAR(255)"
|
||||||
|
]
|
||||||
|
|
||||||
|
print("\n📝 Adding columns...")
|
||||||
|
for sql in location_columns:
|
||||||
|
try:
|
||||||
|
cursor.execute(sql)
|
||||||
|
column_name = sql.split()[4] # Extract column name
|
||||||
|
print(f" ✅ Added: {column_name}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ⚠️ Column may already exist: {e}")
|
||||||
|
|
||||||
|
# Commit changes
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
# Verify columns were added
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'attendance_data'
|
||||||
|
AND column_name IN ('latitude', 'longitude', 'accuracy',
|
||||||
|
'altitude', 'location_source', 'address')
|
||||||
|
ORDER BY column_name;
|
||||||
|
""")
|
||||||
|
|
||||||
|
added_columns = [row[0] for row in cursor.fetchall()]
|
||||||
|
|
||||||
|
print(f"\n📊 Verification:")
|
||||||
|
print(f" ✅ Location columns found: {len(added_columns)}")
|
||||||
|
if added_columns:
|
||||||
|
print(f" 📝 Columns: {', '.join(added_columns)}")
|
||||||
|
|
||||||
|
# Check existing data
|
||||||
|
cursor.execute("SELECT COUNT(*) FROM attendance_data")
|
||||||
|
total_records = cursor.fetchone()[0]
|
||||||
|
print(f" 📊 Total attendance records: {total_records}")
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
print("\n🎉 Database migration completed successfully!")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except psycopg2.Error as e:
|
||||||
|
print(f"❌ Database error: {e}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Unexpected error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_connection():
|
||||||
|
"""Test database connection"""
|
||||||
|
try:
|
||||||
|
conn = psycopg2.connect(DATABASE_URL)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT version();")
|
||||||
|
version = cursor.fetchone()[0]
|
||||||
|
print(f"✅ Database connection successful")
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Database connection failed: {e}")
|
||||||
|
print(f" Check your DATABASE_URL: {DATABASE_URL}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("📍 QR Code System - Geolocation Migration")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Test connection first
|
||||||
|
if not test_connection():
|
||||||
|
print("\n❌ Cannot connect to database. Please check:")
|
||||||
|
print("1. PostgreSQL is running")
|
||||||
|
print("2. Database credentials are correct")
|
||||||
|
print("3. Database exists")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Run migration
|
||||||
|
success = add_location_columns()
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print("\n✅ Ready for geolocation integration!")
|
||||||
|
print("\nNext steps:")
|
||||||
|
print("1. Replace your qr_destination.html template")
|
||||||
|
print("2. Replace your qr_destination.js file")
|
||||||
|
print("3. Update your qr_checkin route in app.py")
|
||||||
|
print("4. Restart your Flask application")
|
||||||
|
print("5. Test geolocation on a mobile device")
|
||||||
|
else:
|
||||||
|
print("\n❌ Migration failed. Please check the errors above.")
|
||||||
|
print("\nYou can also add the columns manually:")
|
||||||
|
print("ALTER TABLE attendance_data ADD COLUMN latitude FLOAT;")
|
||||||
|
print("ALTER TABLE attendance_data ADD COLUMN longitude FLOAT;")
|
||||||
|
print("ALTER TABLE attendance_data ADD COLUMN accuracy FLOAT;")
|
||||||
|
print("ALTER TABLE attendance_data ADD COLUMN altitude FLOAT;")
|
||||||
|
print("ALTER TABLE attendance_data ADD COLUMN location_source VARCHAR(50) DEFAULT 'manual';")
|
||||||
|
print("ALTER TABLE attendance_data ADD COLUMN address VARCHAR(255);")
|
||||||
|
|
||||||
|
print("\n" + "=" * 50)
|
||||||
+579
-375
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,91 @@
|
|||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/qr_destination.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/qr_destination.css') }}">
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<style>
|
||||||
|
/* Additional styles for location tracking */
|
||||||
|
.location-status {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
margin: 15px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-status.loading {
|
||||||
|
background: #d1ecf1;
|
||||||
|
border-color: #bee5eb;
|
||||||
|
color: #0c5460;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-status.success {
|
||||||
|
background: #d4edda;
|
||||||
|
border-color: #c3e6cb;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-status.error {
|
||||||
|
background: #f8d7da;
|
||||||
|
border-color: #f5c6cb;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-info {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 15px 0;
|
||||||
|
display: none;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-info h4 {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coord-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 4px 0;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coord-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coord-value {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-controls {
|
||||||
|
text-align: center;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-small {
|
||||||
|
background: #6c757d;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 0 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-small:hover {
|
||||||
|
background: #545b62;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="destination-container">
|
<div class="destination-container">
|
||||||
@@ -74,6 +159,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- LOCATION STATUS - NEW ADDITION -->
|
||||||
|
<div id="locationStatus" class="location-status">
|
||||||
|
<i class="fas fa-location-arrow"></i>
|
||||||
|
<span id="locationMessage">Getting your location...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- LOCATION DETAILS - NEW ADDITION -->
|
||||||
|
<div id="locationInfo" class="location-info">
|
||||||
|
<h4><i class="fas fa-map-marker-alt"></i> Location Details</h4>
|
||||||
|
<div class="coord-row">
|
||||||
|
<span>Latitude:</span>
|
||||||
|
<span class="coord-value" id="displayLatitude">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="coord-row">
|
||||||
|
<span>Longitude:</span>
|
||||||
|
<span class="coord-value" id="displayLongitude">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="coord-row">
|
||||||
|
<span>Accuracy:</span>
|
||||||
|
<span class="coord-value" id="displayAccuracy">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="coord-row">
|
||||||
|
<span>Address:</span>
|
||||||
|
<span class="coord-value" id="displayAddress">-</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- LOCATION CONTROLS - NEW ADDITION -->
|
||||||
|
<div class="location-controls">
|
||||||
|
<button type="button" class="btn-small" onclick="retryLocationRequest()">
|
||||||
|
<i class="fas fa-redo"></i> Retry Location
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn-small" onclick="toggleLocationInfo()">
|
||||||
|
<i class="fas fa-info-circle"></i> Show Details
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Check-in Form -->
|
<!-- Check-in Form -->
|
||||||
<div class="checkin-card">
|
<div class="checkin-card">
|
||||||
<div class="checkin-header">
|
<div class="checkin-header">
|
||||||
@@ -85,6 +207,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="checkinForm" class="checkin-form">
|
<form id="checkinForm" class="checkin-form">
|
||||||
|
<!-- HIDDEN LOCATION FIELDS - NEW ADDITION -->
|
||||||
|
<input type="hidden" id="latitude" name="latitude">
|
||||||
|
<input type="hidden" id="longitude" name="longitude">
|
||||||
|
<input type="hidden" id="accuracy" name="accuracy">
|
||||||
|
<input type="hidden" id="altitude" name="altitude">
|
||||||
|
<input type="hidden" id="locationSource" name="location_source" value="manual">
|
||||||
|
<input type="hidden" id="address" name="address">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="employee_id">
|
<label for="employee_id">
|
||||||
<i class="fas fa-id-badge"></i>
|
<i class="fas fa-id-badge"></i>
|
||||||
@@ -147,6 +277,11 @@
|
|||||||
<strong>Date:</strong>
|
<strong>Date:</strong>
|
||||||
<span id="successDate">-</span>
|
<span id="successDate">-</span>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- NEW LOCATION SUCCESS INFO -->
|
||||||
|
<div class="success-item" id="successLocationInfo" style="display: none;">
|
||||||
|
<strong>GPS Location:</strong>
|
||||||
|
<span id="successGpsInfo">-</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="success-actions">
|
<div class="success-actions">
|
||||||
@@ -177,10 +312,238 @@
|
|||||||
|
|
||||||
<script src="{{ url_for('static', filename='js/qr_destination.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/qr_destination.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
// Initialize page with QR code URL
|
// Initialize page with QR code URL (EXISTING CODE)
|
||||||
window.qrUrl = '{{ qr_code.qr_url }}';
|
window.qrUrl = '{{ qr_code.qr_url }}';
|
||||||
window.locationName = '{{ qr_code.location }}';
|
window.locationName = '{{ qr_code.location }}';
|
||||||
window.eventName = '{{ qr_code.location_event }}';
|
window.eventName = '{{ qr_code.location_event }}';
|
||||||
|
|
||||||
|
// GEOLOCATION INTEGRATION - NEW CODE
|
||||||
|
let userLocation = {
|
||||||
|
latitude: null,
|
||||||
|
longitude: null,
|
||||||
|
accuracy: null,
|
||||||
|
altitude: null,
|
||||||
|
timestamp: null,
|
||||||
|
source: 'manual',
|
||||||
|
address: null
|
||||||
|
};
|
||||||
|
|
||||||
|
let locationRequestActive = false;
|
||||||
|
|
||||||
|
// Add geolocation to existing page initialization
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('📍 Adding geolocation to existing QR system...');
|
||||||
|
|
||||||
|
// Check if geolocation is supported
|
||||||
|
if (navigator.geolocation) {
|
||||||
|
requestUserLocation();
|
||||||
|
} else {
|
||||||
|
showLocationStatus('error', 'Location not supported by this browser');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request user location
|
||||||
|
*/
|
||||||
|
function requestUserLocation() {
|
||||||
|
if (locationRequestActive) return;
|
||||||
|
|
||||||
|
locationRequestActive = true;
|
||||||
|
showLocationStatus('loading', 'Getting your location...');
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
enableHighAccuracy: true,
|
||||||
|
timeout: 15000,
|
||||||
|
maximumAge: 300000
|
||||||
|
};
|
||||||
|
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
handleLocationSuccess,
|
||||||
|
handleLocationError,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle successful location
|
||||||
|
*/
|
||||||
|
function handleLocationSuccess(position) {
|
||||||
|
locationRequestActive = false;
|
||||||
|
|
||||||
|
const coords = position.coords;
|
||||||
|
console.log('✅ Location obtained:', coords);
|
||||||
|
|
||||||
|
// Store location data
|
||||||
|
userLocation = {
|
||||||
|
latitude: coords.latitude,
|
||||||
|
longitude: coords.longitude,
|
||||||
|
accuracy: coords.accuracy,
|
||||||
|
altitude: coords.altitude,
|
||||||
|
timestamp: position.timestamp,
|
||||||
|
source: 'gps',
|
||||||
|
address: null
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update hidden form fields
|
||||||
|
updateLocationFormFields();
|
||||||
|
|
||||||
|
// Update display
|
||||||
|
updateLocationDisplay();
|
||||||
|
|
||||||
|
// Show success status
|
||||||
|
const accuracyText = coords.accuracy ? Math.round(coords.accuracy) : 'unknown';
|
||||||
|
showLocationStatus('success', `Location captured (±${accuracyText}m accuracy)`);
|
||||||
|
|
||||||
|
// Try to get address
|
||||||
|
reverseGeocodeLocation(coords.latitude, coords.longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle location errors
|
||||||
|
*/
|
||||||
|
function handleLocationError(error) {
|
||||||
|
locationRequestActive = false;
|
||||||
|
|
||||||
|
let message = 'Unable to get location';
|
||||||
|
|
||||||
|
switch(error.code) {
|
||||||
|
case error.PERMISSION_DENIED:
|
||||||
|
message = 'Location access denied - enable in browser settings';
|
||||||
|
break;
|
||||||
|
case error.POSITION_UNAVAILABLE:
|
||||||
|
message = 'Location unavailable - GPS signal weak';
|
||||||
|
break;
|
||||||
|
case error.TIMEOUT:
|
||||||
|
message = 'Location request timed out';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
showLocationStatus('error', `${message} - check-in will work without location`);
|
||||||
|
userLocation.source = 'manual';
|
||||||
|
updateLocationFormFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update form fields with location data
|
||||||
|
*/
|
||||||
|
function updateLocationFormFields() {
|
||||||
|
document.getElementById('latitude').value = userLocation.latitude || '';
|
||||||
|
document.getElementById('longitude').value = userLocation.longitude || '';
|
||||||
|
document.getElementById('accuracy').value = userLocation.accuracy || '';
|
||||||
|
document.getElementById('altitude').value = userLocation.altitude || '';
|
||||||
|
document.getElementById('locationSource').value = userLocation.source || 'manual';
|
||||||
|
document.getElementById('address').value = userLocation.address || '';
|
||||||
|
|
||||||
|
console.log('📝 Updated form fields with location data');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update location display
|
||||||
|
*/
|
||||||
|
function updateLocationDisplay() {
|
||||||
|
if (userLocation.latitude && userLocation.longitude) {
|
||||||
|
document.getElementById('displayLatitude').textContent = userLocation.latitude.toFixed(6);
|
||||||
|
document.getElementById('displayLongitude').textContent = userLocation.longitude.toFixed(6);
|
||||||
|
document.getElementById('displayAccuracy').textContent =
|
||||||
|
userLocation.accuracy ? `±${Math.round(userLocation.accuracy)}m` : 'Unknown';
|
||||||
|
document.getElementById('displayAddress').textContent = userLocation.address || 'Loading...';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get address from coordinates
|
||||||
|
*/
|
||||||
|
function reverseGeocodeLocation(lat, lng) {
|
||||||
|
fetch(`https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${lat}&longitude=${lng}&localityLanguage=en`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data && data.locality) {
|
||||||
|
userLocation.address = data.locality;
|
||||||
|
document.getElementById('address').value = data.locality;
|
||||||
|
document.getElementById('displayAddress').textContent = data.locality;
|
||||||
|
console.log('🏠 Address found:', data.locality);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log('⚠️ Address lookup failed:', error);
|
||||||
|
document.getElementById('displayAddress').textContent = 'Address not available';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show location status
|
||||||
|
*/
|
||||||
|
function showLocationStatus(type, message) {
|
||||||
|
const statusElement = document.getElementById('locationStatus');
|
||||||
|
const messageElement = document.getElementById('locationMessage');
|
||||||
|
|
||||||
|
statusElement.className = `location-status ${type}`;
|
||||||
|
statusElement.style.display = 'flex';
|
||||||
|
|
||||||
|
let icon = '📡';
|
||||||
|
if (type === 'success') icon = '✅';
|
||||||
|
if (type === 'error') icon = '⚠️';
|
||||||
|
|
||||||
|
messageElement.innerHTML = `${icon} ${message}`;
|
||||||
|
|
||||||
|
// Auto-hide after 8 seconds unless it's loading
|
||||||
|
if (type !== 'loading') {
|
||||||
|
setTimeout(() => {
|
||||||
|
statusElement.style.display = 'none';
|
||||||
|
}, 8000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle location info display
|
||||||
|
*/
|
||||||
|
function toggleLocationInfo() {
|
||||||
|
const locationInfo = document.getElementById('locationInfo');
|
||||||
|
if (locationInfo.style.display === 'none' || !locationInfo.style.display) {
|
||||||
|
updateLocationDisplay();
|
||||||
|
locationInfo.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
locationInfo.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retry location request
|
||||||
|
*/
|
||||||
|
function retryLocationRequest() {
|
||||||
|
userLocation = {
|
||||||
|
latitude: null,
|
||||||
|
longitude: null,
|
||||||
|
accuracy: null,
|
||||||
|
altitude: null,
|
||||||
|
timestamp: null,
|
||||||
|
source: 'manual',
|
||||||
|
address: null
|
||||||
|
};
|
||||||
|
requestUserLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook into existing form submission to include location data
|
||||||
|
const originalSubmitCheckin = window.submitCheckin;
|
||||||
|
if (typeof originalSubmitCheckin === 'function') {
|
||||||
|
window.submitCheckin = function(employeeId) {
|
||||||
|
// Ensure form has latest location data
|
||||||
|
updateLocationFormFields();
|
||||||
|
|
||||||
|
console.log('📤 Submitting with location data:', {
|
||||||
|
employee_id: employeeId,
|
||||||
|
latitude: userLocation.latitude,
|
||||||
|
longitude: userLocation.longitude,
|
||||||
|
accuracy: userLocation.accuracy,
|
||||||
|
source: userLocation.source
|
||||||
|
});
|
||||||
|
|
||||||
|
// Call original function
|
||||||
|
return originalSubmitCheckin(employeeId);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📍 Geolocation integration completed successfully!');
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user