Fix attendance report filter button issues
This commit is contained in:
@@ -1233,7 +1233,7 @@ def login():
|
||||
try:
|
||||
# Find user (case-insensitive username)
|
||||
user = User.query.filter(
|
||||
User.username.ilike(username),
|
||||
User.username.like(username),
|
||||
User.active_status == True
|
||||
).first()
|
||||
|
||||
@@ -2405,7 +2405,7 @@ def create_qr_code():
|
||||
'create',
|
||||
additional_info={'location_event': location_event}
|
||||
)
|
||||
|
||||
|
||||
# Success message with coordinates info
|
||||
project_info = f" in project '{project.name}'" if project else ""
|
||||
coord_info = f" with coordinates ({new_qr_code.coordinates_display})" if has_coordinates else ""
|
||||
@@ -2997,6 +2997,12 @@ def attendance_report():
|
||||
try:
|
||||
print("📊 Loading attendance report...")
|
||||
|
||||
# Log attendance report access
|
||||
try:
|
||||
logger_handler.logger.info(f"User {session.get('username', 'unknown')} accessed attendance report")
|
||||
except Exception as log_error:
|
||||
print(f"⚠️ Logging error (non-critical): {log_error}")
|
||||
|
||||
# Check if location_accuracy column exists
|
||||
has_location_accuracy = check_location_accuracy_column_exists()
|
||||
print(f"🔍 Location accuracy column exists: {has_location_accuracy}")
|
||||
@@ -3066,17 +3072,18 @@ def attendance_report():
|
||||
|
||||
# Apply location filter
|
||||
if location_filter:
|
||||
conditions.append("ad.location_name ILIKE :location")
|
||||
conditions.append("ad.location_name LIKE :location")
|
||||
params['location'] = f"%{location_filter}%"
|
||||
|
||||
# Apply employee filter
|
||||
if employee_filter:
|
||||
conditions.append("ad.employee_id ILIKE :employee")
|
||||
conditions.append("ad.employee_id LIKE :employee")
|
||||
params['employee'] = f"%{employee_filter}%"
|
||||
|
||||
# FIXED: Apply project filter using SQL approach (not ORM)
|
||||
if project_filter:
|
||||
# Join with QRCode and filter by project_id
|
||||
query = query.join(QRCode, AttendanceData.qr_code_id == QRCode.id).filter(QRCode.project_id == int(project_filter))
|
||||
conditions.append("qc.project_id = :project_id")
|
||||
params['project_id'] = int(project_filter)
|
||||
print(f"📊 Applied project filter: {project_filter}")
|
||||
|
||||
# Add conditions to query
|
||||
@@ -3100,80 +3107,78 @@ def attendance_report():
|
||||
# Safe attribute access with fallbacks
|
||||
location_accuracy = getattr(record, 'location_accuracy', None)
|
||||
gps_accuracy = getattr(record, 'gps_accuracy', None)
|
||||
qr_address = getattr(record, 'qr_address', None)
|
||||
|
||||
# Handle location accuracy for address display logic
|
||||
if location_accuracy is not None and location_accuracy != "None":
|
||||
try:
|
||||
accuracy_value = float(location_accuracy) if isinstance(location_accuracy, str) else location_accuracy
|
||||
checked_in_address = qr_address if (accuracy_value <= 0.5) else getattr(record, 'checked_in_address', None)
|
||||
except (ValueError, TypeError):
|
||||
checked_in_address = getattr(record, 'checked_in_address', None)
|
||||
else:
|
||||
checked_in_address = getattr(record, 'checked_in_address', None)
|
||||
|
||||
# CRITICAL FIX: Properly handle check_in_time formatting
|
||||
check_in_time_value = record.check_in_time
|
||||
|
||||
# Handle different possible types for check_in_time
|
||||
if isinstance(check_in_time_value, timedelta):
|
||||
# Convert timedelta to time object
|
||||
total_seconds = int(check_in_time_value.total_seconds())
|
||||
hours = total_seconds // 3600
|
||||
minutes = (total_seconds % 3600) // 60
|
||||
seconds = total_seconds % 60
|
||||
formatted_time = time(hours % 24, minutes, seconds)
|
||||
print(f"⚠️ Converted timedelta to time: {check_in_time_value} -> {formatted_time}")
|
||||
elif isinstance(check_in_time_value, time):
|
||||
# Already a time object, use as-is
|
||||
formatted_time = check_in_time_value
|
||||
elif isinstance(check_in_time_value, datetime):
|
||||
# Extract time component from datetime
|
||||
formatted_time = check_in_time_value.time()
|
||||
elif isinstance(check_in_time_value, str):
|
||||
# Try to parse string to time
|
||||
try:
|
||||
formatted_time = datetime.strptime(check_in_time_value, '%H:%M:%S').time()
|
||||
except ValueError:
|
||||
try:
|
||||
formatted_time = datetime.strptime(check_in_time_value, '%H:%M').time()
|
||||
except ValueError:
|
||||
# Fallback to current time if parsing fails
|
||||
formatted_time = datetime.now().time()
|
||||
print(f"⚠️ Could not parse time string: {check_in_time_value}, using current time")
|
||||
else:
|
||||
# Fallback to current time for any other type
|
||||
formatted_time = datetime.now().time()
|
||||
print(f"⚠️ Unexpected check_in_time type: {type(check_in_time_value)}, using current time")
|
||||
|
||||
# Create the record dictionary with properly formatted time
|
||||
record_dict = {
|
||||
# Create processed record with calculated fields
|
||||
processed_record = {
|
||||
'id': record.id,
|
||||
'employee_id': record.employee_id,
|
||||
'employee_id': record.employee_id or 'Unknown',
|
||||
'check_in_date': record.check_in_date,
|
||||
'check_in_time': formatted_time, # Now guaranteed to be a time object
|
||||
'location_name': record.location_name,
|
||||
'location_event': getattr(record, 'location_event', ''),
|
||||
'qr_address': qr_address or 'Not available',
|
||||
'checked_in_address': checked_in_address or 'Location not captured',
|
||||
'device_info': getattr(record, 'device_info', ''),
|
||||
'check_in_time': record.check_in_time,
|
||||
'location_name': record.location_name or 'Unknown Location',
|
||||
'location_event': getattr(record, 'location_event', None) or 'Check In',
|
||||
'qr_address': getattr(record, 'qr_address', None) or 'N/A',
|
||||
'checked_in_address': getattr(record, 'checked_in_address', None) or 'N/A',
|
||||
'latitude': record.latitude,
|
||||
'longitude': record.longitude,
|
||||
'location_accuracy': location_accuracy,
|
||||
'gps_accuracy': gps_accuracy,
|
||||
'accuracy_level': get_location_accuracy_level(location_accuracy) if location_accuracy else 'unknown',
|
||||
'has_location_data': record.latitude is not None and record.longitude is not None,
|
||||
'coordinates': f"{record.latitude:.10f}, {record.longitude:.10f}" if record.latitude and record.longitude else "No GPS data",
|
||||
'has_location_accuracy_feature': has_location_accuracy
|
||||
'device_info': getattr(record, 'device_info', None) or 'Unknown Device',
|
||||
}
|
||||
processed_records.append(record_dict)
|
||||
|
||||
print(f"✅ Processed {len(processed_records)} records")
|
||||
|
||||
# FIXED: Add address display logic based on location accuracy
|
||||
# If location accuracy <= 0.5 miles, display QR address; otherwise display actual check-in address
|
||||
if location_accuracy is not None:
|
||||
try:
|
||||
accuracy_value = float(location_accuracy) if isinstance(location_accuracy, str) else location_accuracy
|
||||
if accuracy_value <= 0.5:
|
||||
# High accuracy - use QR code address
|
||||
processed_record['display_address'] = getattr(record, 'qr_address', None) or 'N/A'
|
||||
processed_record['address_source'] = 'qr'
|
||||
print(f"📍 Using QR address for employee {record.employee_id} (accuracy: {accuracy_value:.3f} miles)")
|
||||
else:
|
||||
# Lower accuracy - use actual check-in address
|
||||
processed_record['display_address'] = getattr(record, 'checked_in_address', None) or 'N/A'
|
||||
processed_record['address_source'] = 'checkin'
|
||||
print(f"📍 Using check-in address for employee {record.employee_id} (accuracy: {accuracy_value:.3f} miles)")
|
||||
except (ValueError, TypeError):
|
||||
# If accuracy can't be converted to float, use check-in address
|
||||
processed_record['display_address'] = getattr(record, 'checked_in_address', None) or 'N/A'
|
||||
processed_record['address_source'] = 'checkin'
|
||||
else:
|
||||
# No location accuracy data - use actual check-in address
|
||||
processed_record['display_address'] = getattr(record, 'checked_in_address', None) or 'N/A'
|
||||
processed_record['address_source'] = 'checkin'
|
||||
|
||||
# Add accuracy level calculation if location_accuracy exists
|
||||
if location_accuracy is not None:
|
||||
if location_accuracy <= 10:
|
||||
processed_record['accuracy_level'] = 'High'
|
||||
elif location_accuracy <= 50:
|
||||
processed_record['accuracy_level'] = 'Medium'
|
||||
else:
|
||||
processed_record['accuracy_level'] = 'Low'
|
||||
else:
|
||||
processed_record['accuracy_level'] = 'Unknown'
|
||||
|
||||
# Add formatted datetime for display
|
||||
try:
|
||||
if record.check_in_date and record.check_in_time:
|
||||
datetime_obj = datetime.combine(record.check_in_date, record.check_in_time)
|
||||
processed_record['formatted_datetime'] = datetime_obj.strftime('%m/%d/%Y %I:%M %p')
|
||||
else:
|
||||
processed_record['formatted_datetime'] = 'Invalid Date/Time'
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error formatting datetime for record {record.id}: {e}")
|
||||
processed_record['formatted_datetime'] = 'Error'
|
||||
|
||||
processed_records.append(processed_record)
|
||||
|
||||
# Get unique locations for filter dropdown
|
||||
try:
|
||||
locations_query = db.session.execute(text("""
|
||||
SELECT DISTINCT location_name
|
||||
FROM attendance_data
|
||||
WHERE location_name IS NOT NULL
|
||||
WHERE location_name IS NOT NULL
|
||||
ORDER BY location_name
|
||||
"""))
|
||||
locations = [row[0] for row in locations_query.fetchall()]
|
||||
@@ -3182,7 +3187,7 @@ def attendance_report():
|
||||
print(f"⚠️ Error loading locations: {e}")
|
||||
locations = []
|
||||
|
||||
# Update the locations query to get only active projects for the dropdown
|
||||
# Update the projects query to get only active projects for the dropdown
|
||||
try:
|
||||
projects = db.session.execute(text("""
|
||||
SELECT p.id, p.name, COUNT(DISTINCT ad.id) as attendance_count
|
||||
@@ -3242,7 +3247,7 @@ def attendance_report():
|
||||
})()
|
||||
|
||||
# Add today's date for template
|
||||
today_date = datetime.now().strftime('%m/%d/%Y')
|
||||
today_date = datetime.now().strftime('%Y-%m-%d')
|
||||
current_date_formatted = datetime.now().strftime('%B %d')
|
||||
|
||||
print("✅ Rendering attendance report template")
|
||||
@@ -3267,6 +3272,12 @@ def attendance_report():
|
||||
import traceback
|
||||
print(f"❌ Traceback: {traceback.format_exc()}")
|
||||
|
||||
# Log the error
|
||||
try:
|
||||
logger_handler.log_database_error('attendance_report', e)
|
||||
except Exception as log_error:
|
||||
print(f"⚠️ Additional logging error: {log_error}")
|
||||
|
||||
flash('Error loading attendance report. Please check the server logs for details.', 'error')
|
||||
return redirect(url_for('dashboard'))
|
||||
|
||||
@@ -3693,12 +3704,12 @@ def create_excel_export(selected_columns, column_names, filters):
|
||||
|
||||
# Apply location filter
|
||||
if filters.get('location_filter'):
|
||||
query = query.filter(AttendanceData.location_name.ilike(f"%{filters['location_filter']}%"))
|
||||
query = query.filter(AttendanceData.location_name.like(f"%{filters['location_filter']}%"))
|
||||
print(f"📊 Applied location filter: {filters['location_filter']}")
|
||||
|
||||
# Apply employee filter
|
||||
if filters.get('employee_filter'):
|
||||
query = query.filter(AttendanceData.employee_id.ilike(f"%{filters['employee_filter']}%"))
|
||||
query = query.filter(AttendanceData.employee_id.like(f"%{filters['employee_filter']}%"))
|
||||
print(f"📊 Applied employee filter: {filters['employee_filter']}")
|
||||
|
||||
# Apply project filter
|
||||
@@ -3856,12 +3867,12 @@ def create_excel_export_ordered(selected_columns, column_names, filters):
|
||||
|
||||
# Apply location filter
|
||||
if filters.get('location_filter'):
|
||||
query = query.filter(AttendanceData.location_name.ilike(f"%{filters['location_filter']}%"))
|
||||
query = query.filter(AttendanceData.location_name.like(f"%{filters['location_filter']}%"))
|
||||
print(f"📊 Applied location filter: {filters['location_filter']}")
|
||||
|
||||
# Apply employee filter
|
||||
if filters.get('employee_filter'):
|
||||
query = query.filter(AttendanceData.employee_id.ilike(f"%{filters['employee_filter']}%"))
|
||||
query = query.filter(AttendanceData.employee_id.like(f"%{filters['employee_filter']}%"))
|
||||
print(f"📊 Applied employee filter: {filters['employee_filter']}")
|
||||
|
||||
# Execute query and get results
|
||||
|
||||
+208
-136
@@ -43,6 +43,31 @@ function loadTableData() {
|
||||
const rows = table.querySelectorAll("tbody tr");
|
||||
attendanceData = Array.from(rows).map((row, index) => {
|
||||
const cells = row.querySelectorAll("td");
|
||||
|
||||
// Debug: Log the actual cell content
|
||||
if (index < 3) {
|
||||
// Only log first 3 rows for debugging
|
||||
console.log(`=== DEBUGGING ROW ${index + 1} ===`);
|
||||
console.log(
|
||||
`Cell 7 (check-in address):`,
|
||||
cells[7] ? cells[7].innerHTML : "NOT FOUND"
|
||||
);
|
||||
console.log(
|
||||
`Cell 8 (accuracy):`,
|
||||
cells[8] ? cells[8].innerHTML : "NOT FOUND"
|
||||
);
|
||||
|
||||
if (cells[8]) {
|
||||
const accuracyText = cells[8].textContent;
|
||||
console.log(`Accuracy text:`, accuracyText);
|
||||
|
||||
const milesMatch = accuracyText.match(/(\d+\.?\d*)\s*mi/);
|
||||
const metersMatch = accuracyText.match(/(\d+\.?\d*)\s*m/);
|
||||
console.log(`Miles match:`, milesMatch);
|
||||
console.log(`Meters match:`, metersMatch);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: row.dataset.recordId,
|
||||
index: index + 1,
|
||||
@@ -57,13 +82,16 @@ function loadTableData() {
|
||||
checked_in_address: cells[7]
|
||||
? cells[7].getAttribute("title") || cells[7].textContent.trim()
|
||||
: "",
|
||||
accuracy: cells[8] ? extractAccuracyValue(cells[8]) : null,
|
||||
accuracy_level: cells[8] ? extractAccuracyLevel(cells[8]) : "unknown",
|
||||
// FIXED: Extract location accuracy for address display logic
|
||||
location_accuracy: cells[8] ? extractLocationAccuracy(cells[8]) : null,
|
||||
accuracy_level: cells[8]
|
||||
? extractLocationAccuracyLevel(cells[8])
|
||||
: "unknown",
|
||||
device: cells[9]
|
||||
? cells[9].getAttribute("title") || cells[9].textContent.trim()
|
||||
: "",
|
||||
has_location_data: cells[8]
|
||||
? !cells[8].textContent.includes("No GPS")
|
||||
? !cells[8].textContent.includes("Unknown")
|
||||
: false,
|
||||
coordinates: extractCoordinates(cells[8]),
|
||||
};
|
||||
@@ -71,6 +99,29 @@ function loadTableData() {
|
||||
|
||||
filteredData = [...attendanceData];
|
||||
console.log(`Loaded ${attendanceData.length} attendance records`);
|
||||
|
||||
// Debug log for location accuracy data
|
||||
const recordsWithAccuracy = attendanceData.filter(
|
||||
(r) => r.location_accuracy !== null
|
||||
);
|
||||
console.log(
|
||||
`Records with location accuracy: ${recordsWithAccuracy.length}`
|
||||
);
|
||||
if (recordsWithAccuracy.length > 0) {
|
||||
console.log(
|
||||
`Sample records with accuracy:`,
|
||||
recordsWithAccuracy.slice(0, 3).map((r) => ({
|
||||
employeeId: r.employeeId,
|
||||
location_accuracy: r.location_accuracy,
|
||||
accuracy_level: r.accuracy_level,
|
||||
qr_address: r.qr_address,
|
||||
checked_in_address: r.checked_in_address,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// Log all first 3 records for debugging
|
||||
console.log("First 3 attendance records:", attendanceData.slice(0, 3));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,123 +340,6 @@ function updateTable() {
|
||||
updateFilterStats();
|
||||
}
|
||||
|
||||
function createTableRow(record, displayIndex) {
|
||||
const row = document.createElement("tr");
|
||||
row.dataset.recordId = record.id;
|
||||
|
||||
// Create accuracy badge HTML
|
||||
const accuracyBadge =
|
||||
record.accuracy !== null
|
||||
? `<span class="accuracy-badge accuracy-${
|
||||
record.accuracy_level
|
||||
}" title="GPS accuracy: ${record.accuracy}m">
|
||||
<i class="fas fa-crosshairs"></i>
|
||||
${record.accuracy.toFixed(1)}m
|
||||
<small>(${record.accuracy_level})</small>
|
||||
</span>`
|
||||
: `<span class="accuracy-badge accuracy-unknown" title="No GPS data available">
|
||||
<i class="fas fa-question-circle"></i>
|
||||
No GPS
|
||||
</span>`;
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${displayIndex}</td>
|
||||
<td>
|
||||
<div class="employee-info">
|
||||
<span class="employee-id">${record.employeeId}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="location-info">
|
||||
<i class="fas fa-map-marker-alt"></i>
|
||||
${record.location}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="event-info">
|
||||
${record.event}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="date-info">
|
||||
${record.date}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="time-info">
|
||||
${record.time}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="address-info qr-address">
|
||||
<i class="fas fa-qrcode"></i>
|
||||
<span title="${record.qr_address}">
|
||||
${
|
||||
record.qr_address.length > 50
|
||||
? record.qr_address.substring(0, 50) + "..."
|
||||
: record.qr_address
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="address-info checkin-address">
|
||||
<i class="fas fa-location-arrow"></i>
|
||||
<span title="${record.checked_in_address}">
|
||||
${
|
||||
record.checked_in_address.length > 50
|
||||
? record.checked_in_address.substring(0, 50) + "..."
|
||||
: record.checked_in_address
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="accuracy-info">
|
||||
${accuracyBadge}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="device-info">
|
||||
<i class="fas fa-mobile-alt"></i>
|
||||
<span title="${record.device}">
|
||||
${
|
||||
record.device.length > 20
|
||||
? record.device.substring(0, 20) + "..."
|
||||
: record.device
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="record-actions">
|
||||
${
|
||||
hasEditPermission
|
||||
? `
|
||||
<button onclick="editRecord('${record.id}')"
|
||||
class="action-btn btn-edit"
|
||||
title="Edit Record">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button onclick="deleteRecord('${record.id}', '${record.employeeId}')"
|
||||
class="action-btn btn-delete"
|
||||
title="Delete Record">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
`
|
||||
: `
|
||||
<span class="text-muted" title="Admin or Payroll access required">
|
||||
<i class="fas fa-lock"></i>
|
||||
</span>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
function changeEntriesPerPage() {
|
||||
const select = document.getElementById("entriesPerPage");
|
||||
entriesPerPage = select.value === "all" ? "all" : parseInt(select.value);
|
||||
@@ -647,6 +581,7 @@ function loadTableData() {
|
||||
checked_in_address: cells[7]
|
||||
? cells[7].getAttribute("title") || cells[7].textContent.trim()
|
||||
: "",
|
||||
// FIXED: Extract location accuracy for address display logic
|
||||
location_accuracy: cells[8] ? extractLocationAccuracy(cells[8]) : null,
|
||||
accuracy_level: cells[8]
|
||||
? extractLocationAccuracyLevel(cells[8])
|
||||
@@ -662,29 +597,91 @@ function loadTableData() {
|
||||
});
|
||||
|
||||
filteredData = [...attendanceData];
|
||||
console.log(
|
||||
`Loaded ${attendanceData.length} attendance records with location accuracy`
|
||||
console.log(`Loaded ${attendanceData.length} attendance records`);
|
||||
|
||||
// Debug log for location accuracy data
|
||||
const recordsWithAccuracy = attendanceData.filter(
|
||||
(r) => r.location_accuracy !== null
|
||||
);
|
||||
console.log(
|
||||
`Records with location accuracy: ${recordsWithAccuracy.length}`
|
||||
);
|
||||
if (recordsWithAccuracy.length > 0) {
|
||||
console.log(
|
||||
`Sample location accuracy values:`,
|
||||
recordsWithAccuracy.slice(0, 3).map((r) => r.location_accuracy)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function extractLocationAccuracy(cell) {
|
||||
const text = cell.textContent;
|
||||
const match = text.match(/(\d+\.?\d*)\s*mi/);
|
||||
return match ? parseFloat(match[1]) : null;
|
||||
console.log(`Extracting accuracy from: "${text}"`);
|
||||
|
||||
// Look for miles pattern (e.g., "0.003 mi", "1.234 mi")
|
||||
const milesMatch = text.match(/(\d+\.?\d*)\s*mi/);
|
||||
if (milesMatch) {
|
||||
const value = parseFloat(milesMatch[1]);
|
||||
console.log(`Found miles: ${value}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Look for specific accuracy patterns in the HTML
|
||||
const accuracyMatch = text.match(/accuracy[:\s]*(\d+\.?\d*)/i);
|
||||
if (accuracyMatch) {
|
||||
const value = parseFloat(accuracyMatch[1]);
|
||||
console.log(`Found accuracy: ${value}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Check for data attributes
|
||||
const dataAccuracy = cell.getAttribute("data-accuracy");
|
||||
if (dataAccuracy) {
|
||||
const value = parseFloat(dataAccuracy);
|
||||
console.log(`Found data-accuracy: ${value}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Fallback: look for GPS accuracy in meters and convert to miles (approximate)
|
||||
const metersMatch = text.match(/(\d+\.?\d*)\s*m/);
|
||||
if (metersMatch) {
|
||||
const meters = parseFloat(metersMatch[1]);
|
||||
const miles = meters * 0.000621371; // Convert meters to miles (approximate)
|
||||
console.log(`Found meters: ${meters}, converted to miles: ${miles}`);
|
||||
return miles;
|
||||
}
|
||||
|
||||
console.log(`No accuracy found in: "${text}"`);
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractLocationAccuracyLevel(cell) {
|
||||
const text = cell.textContent;
|
||||
if (text.includes("excellent") || text.includes("good")) return "accurate";
|
||||
if (text.includes("fair") || text.includes("poor")) return "inaccurate";
|
||||
return "unknown";
|
||||
const text = cell.textContent.toLowerCase();
|
||||
if (
|
||||
text.includes("high") ||
|
||||
text.includes("excellent") ||
|
||||
text.includes("good")
|
||||
)
|
||||
return "High";
|
||||
if (text.includes("medium") || text.includes("fair")) return "Medium";
|
||||
if (text.includes("low") || text.includes("poor")) return "Low";
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
function createTableRow(record, displayIndex) {
|
||||
const row = document.createElement("tr");
|
||||
row.dataset.recordId = record.id;
|
||||
|
||||
// Debug logging for first few records
|
||||
if (displayIndex <= 3) {
|
||||
console.log(`=== CREATING ROW ${displayIndex} ===`);
|
||||
console.log(`Employee: ${record.employeeId}`);
|
||||
console.log(`Location accuracy: ${record.location_accuracy}`);
|
||||
console.log(`QR address: ${record.qr_address}`);
|
||||
console.log(`Check-in address: ${record.checked_in_address}`);
|
||||
}
|
||||
|
||||
// Create location accuracy badge HTML
|
||||
const locationAccuracyBadge =
|
||||
record.location_accuracy !== null
|
||||
@@ -703,6 +700,88 @@ function createTableRow(record, displayIndex) {
|
||||
Unknown
|
||||
</span>`;
|
||||
|
||||
// FIXED: Address display logic based on location accuracy
|
||||
let addressDisplayHTML = "";
|
||||
let addressToShow = record.checked_in_address;
|
||||
let addressIcon = "fas fa-location-arrow";
|
||||
let addressClass = "address-normal-accuracy";
|
||||
let addressTitle = `Check-in Address: ${record.checked_in_address}`;
|
||||
|
||||
// Apply 0.5-mile threshold logic
|
||||
if (
|
||||
record.location_accuracy !== null &&
|
||||
record.location_accuracy !== undefined
|
||||
) {
|
||||
const accuracy = parseFloat(record.location_accuracy);
|
||||
|
||||
if (displayIndex <= 3) {
|
||||
console.log(`Applying address logic for ${record.employeeId}:`);
|
||||
console.log(` Accuracy value: ${accuracy}`);
|
||||
console.log(` Is <= 0.5? ${accuracy <= 0.5}`);
|
||||
}
|
||||
|
||||
if (!isNaN(accuracy) && accuracy <= 0.5) {
|
||||
// High accuracy - use QR address
|
||||
addressToShow = record.qr_address;
|
||||
addressIcon = "fas fa-check-circle";
|
||||
addressClass = "address-high-accuracy";
|
||||
addressTitle = `QR Address (High Accuracy ≤ 0.5 mi): ${record.qr_address}`;
|
||||
|
||||
if (displayIndex <= 3) {
|
||||
console.log(` → Using QR address: ${addressToShow}`);
|
||||
}
|
||||
|
||||
addressDisplayHTML = `
|
||||
<i class="${addressIcon}" style="color: #059669; margin-right: 4px;"
|
||||
title="High accuracy - showing QR location"></i>
|
||||
<span title="${addressTitle}" class="${addressClass}">
|
||||
${
|
||||
addressToShow.length > 45
|
||||
? addressToShow.substring(0, 45) + "..."
|
||||
: addressToShow
|
||||
}
|
||||
</span>
|
||||
`;
|
||||
} else {
|
||||
// Lower accuracy - use check-in address
|
||||
if (displayIndex <= 3) {
|
||||
console.log(` → Using check-in address: ${addressToShow}`);
|
||||
}
|
||||
|
||||
addressDisplayHTML = `
|
||||
<i class="fas fa-exclamation-triangle" style="color: #f59e0b; margin-right: 4px;"
|
||||
title="Lower accuracy - showing actual check-in location"></i>
|
||||
<span title="${addressTitle} (Accuracy: ${accuracy.toFixed(
|
||||
3
|
||||
)} mi)" class="${addressClass}">
|
||||
${
|
||||
addressToShow.length > 45
|
||||
? addressToShow.substring(0, 45) + "..."
|
||||
: addressToShow
|
||||
}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
} else {
|
||||
// No accuracy data - use check-in address
|
||||
if (displayIndex <= 3) {
|
||||
console.log(
|
||||
` → No accuracy data, using check-in address: ${addressToShow}`
|
||||
);
|
||||
}
|
||||
|
||||
addressDisplayHTML = `
|
||||
<i class="${addressIcon}"></i>
|
||||
<span title="${addressTitle}" class="${addressClass}">
|
||||
${
|
||||
addressToShow.length > 45
|
||||
? addressToShow.substring(0, 45) + "..."
|
||||
: addressToShow
|
||||
}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${displayIndex}</td>
|
||||
<td>
|
||||
@@ -745,14 +824,7 @@ function createTableRow(record, displayIndex) {
|
||||
</td>
|
||||
<td>
|
||||
<div class="address-info checkin-address">
|
||||
<i class="fas fa-location-arrow"></i>
|
||||
<span title="${record.checked_in_address}">
|
||||
${
|
||||
record.checked_in_address.length > 50
|
||||
? record.checked_in_address.substring(0, 50) + "..."
|
||||
: record.checked_in_address
|
||||
}
|
||||
</span>
|
||||
${addressDisplayHTML}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -224,11 +224,27 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="address-column">
|
||||
<div class="address-info qr-address">
|
||||
<i class="fas fa-qrcode"></i>
|
||||
<span title="{{ record.qr_address }}">
|
||||
{{ record.qr_address[:50] }}{% if record.qr_address|length > 50 %}...{% endif %}
|
||||
</span>
|
||||
<div class="address-info checkin-address">
|
||||
<i class="fas fa-location-arrow"></i>
|
||||
{% if record.address_source == 'qr' %}
|
||||
<!-- Using QR address due to high accuracy (≤ 0.5 miles) -->
|
||||
<span title="QR Address (High Accuracy ≤ 0.5 mi): {{ record.qr_address }}"
|
||||
class="address-high-accuracy">
|
||||
<i class="fas fa-check-circle" style="color: #059669; margin-right: 4px;"
|
||||
title="High accuracy - showing QR location"></i>
|
||||
{{ record.qr_address[:45] }}{% if record.qr_address|length > 45 %}...{% endif %}
|
||||
</span>
|
||||
{% else %}
|
||||
<!-- Using actual check-in address due to lower accuracy (> 0.5 miles) or no accuracy data -->
|
||||
<span title="Check-in Address{% if record.location_accuracy %} (Accuracy: {{ '%.3f'|format(record.location_accuracy) }} mi){% endif %}: {{ record.checked_in_address }}"
|
||||
class="address-normal-accuracy">
|
||||
{% if record.location_accuracy and record.location_accuracy > 0.5 %}
|
||||
<i class="fas fa-exclamation-triangle" style="color: #f59e0b; margin-right: 4px;"
|
||||
title="Lower accuracy - showing actual check-in location"></i>
|
||||
{% endif %}
|
||||
{{ record.checked_in_address[:45] }}{% if record.checked_in_address|length > 45 %}...{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="address-column">
|
||||
|
||||
Reference in New Issue
Block a user