code changes
This commit is contained in:
+199
-1
@@ -686,7 +686,7 @@ tr.modified-record {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: var(--z-modal);
|
||||
z-index: 99999 !important;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
@@ -701,6 +701,7 @@ tr.modified-record {
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
max-height: 80vh;
|
||||
z-index: 100000 !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -1365,3 +1366,200 @@ tr.modified-record {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
VERIFICATION REVIEW STYLES
|
||||
============================================ */
|
||||
|
||||
/* Review Needed Badge */
|
||||
.badge-review-needed {
|
||||
background: #fef3c7 !important;
|
||||
color: #92400e !important;
|
||||
border: 1px solid rgba(146, 64, 14, 0.3) !important;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.badge-review-needed:hover {
|
||||
background: #fde68a !important;
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 4px 8px rgba(146, 64, 14, 0.2);
|
||||
}
|
||||
|
||||
/* Verified Badge */
|
||||
.badge-verified {
|
||||
background: #d1fae5 !important;
|
||||
color: #065f46 !important;
|
||||
border: 1px solid rgba(5, 150, 105, 0.3) !important;
|
||||
}
|
||||
|
||||
/* Rejected Badge */
|
||||
.badge-rejected {
|
||||
background: #fee2e2 !important;
|
||||
color: #991b1b !important;
|
||||
border: 1px solid rgba(220, 38, 38, 0.3) !important;
|
||||
}
|
||||
|
||||
/* Review Button in Actions Column */
|
||||
.btn-review {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
border: 1px solid rgba(146, 64, 14, 0.2);
|
||||
}
|
||||
|
||||
.btn-review:hover {
|
||||
background: #92400e;
|
||||
color: #ffffff;
|
||||
border-color: #92400e;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(146, 64, 14, 0.3);
|
||||
}
|
||||
|
||||
/* Verification Photo Modal Specific Styles */
|
||||
#verificationPhotoModal {
|
||||
z-index: 99999 !important;
|
||||
}
|
||||
|
||||
.verification-modal-content {
|
||||
max-width: 900px;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.verification-photo-container {
|
||||
text-align: center;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.verification-photo-large {
|
||||
max-width: 100%;
|
||||
max-height: 500px;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.verification-details-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.verification-detail-card {
|
||||
background: var(--gray-50, #f9fafb);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--gray-200, #e5e7eb);
|
||||
}
|
||||
|
||||
.verification-detail-card h4 {
|
||||
font-size: 0.875rem;
|
||||
color: var(--gray-600, #6b7280);
|
||||
margin: 0 0 0.5rem 0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.verification-detail-card p {
|
||||
font-size: 1rem;
|
||||
color: var(--gray-900, #0f172a);
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.verification-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
margin-top: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid var(--gray-200, #e5e7eb);
|
||||
}
|
||||
|
||||
.btn-approve {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
padding: 0.75rem 2rem;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-approve:hover {
|
||||
background: #059669;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.btn-reject {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
padding: 0.75rem 2rem;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-reject:hover {
|
||||
background: #dc2626;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.verification-status-pending {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Loading State */
|
||||
.verification-loading {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
color: var(--gray-600, #6b7280);
|
||||
}
|
||||
|
||||
.verification-loading i {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Responsive Design for Verification Modal */
|
||||
@media (max-width: 768px) {
|
||||
.verification-modal-content {
|
||||
width: 98%;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.verification-photo-large {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.verification-details-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.verification-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn-approve,
|
||||
.btn-reject {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
+318
-89
@@ -37,89 +37,6 @@ function initializeReport() {
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
function loadTableData() {
|
||||
const table = document.getElementById("attendanceTable");
|
||||
if (table) {
|
||||
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,
|
||||
employeeId: cells[1] ? cells[1].textContent.trim() : "",
|
||||
employeeName: cells[2] ? cells[2].textContent.trim() : "",
|
||||
location: cells[3] ? cells[3].textContent.trim() : "",
|
||||
event: cells[4] ? cells[4].textContent.trim() : "",
|
||||
date: cells[5] ? cells[5].textContent.trim() : "",
|
||||
time: cells[6] ? cells[6].textContent.trim() : "",
|
||||
qr_address: cells[7]
|
||||
? cells[7].getAttribute("title") || cells[7].textContent.trim()
|
||||
: "",
|
||||
checked_in_address: cells[8]
|
||||
? cells[8].getAttribute("title") || cells[8].textContent.trim()
|
||||
: "",
|
||||
location_accuracy: cells[9] ? extractLocationAccuracy(cells[9]) : null,
|
||||
accuracy_level: cells[9]
|
||||
? extractLocationAccuracyLevel(cells[9])
|
||||
: "unknown",
|
||||
device: cells[10]
|
||||
? cells[10].textContent.trim()
|
||||
: ""
|
||||
};
|
||||
});
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
function extractAccuracyValue(cell) {
|
||||
const text = cell.textContent;
|
||||
@@ -563,6 +480,10 @@ function loadTableData() {
|
||||
const rows = table.querySelectorAll("tbody tr");
|
||||
attendanceData = Array.from(rows).map((row, index) => {
|
||||
const cells = row.querySelectorAll("td");
|
||||
|
||||
// Extract verification data from the accuracy badge
|
||||
const verificationData = extractVerificationData(cells[9]);
|
||||
|
||||
return {
|
||||
id: row.dataset.recordId,
|
||||
index: index + 1,
|
||||
@@ -587,6 +508,8 @@ function loadTableData() {
|
||||
? cells[10].textContent.trim()
|
||||
: "",
|
||||
isModified: row.classList.contains('modified-record'),
|
||||
verification_required: verificationData.required,
|
||||
verification_status: verificationData.status
|
||||
};
|
||||
});
|
||||
|
||||
@@ -661,6 +584,37 @@ function extractLocationAccuracyLevel(cell) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function extractVerificationData(cell) {
|
||||
// Extract verification status from badge classes in the HTML
|
||||
if (!cell) {
|
||||
console.log('extractVerificationData: No cell provided');
|
||||
return { required: false, status: null };
|
||||
}
|
||||
|
||||
const badge = cell.querySelector('.location-accuracy-badge');
|
||||
if (!badge) {
|
||||
console.log('extractVerificationData: No badge found in cell');
|
||||
return { required: false, status: null };
|
||||
}
|
||||
|
||||
console.log('extractVerificationData: Badge classes:', badge.className);
|
||||
|
||||
// Check badge classes for verification status
|
||||
if (badge.classList.contains('badge-review-needed')) {
|
||||
console.log('extractVerificationData: Found pending verification');
|
||||
return { required: true, status: 'pending' };
|
||||
} else if (badge.classList.contains('badge-verified')) {
|
||||
console.log('extractVerificationData: Found approved verification');
|
||||
return { required: true, status: 'approved' };
|
||||
} else if (badge.classList.contains('badge-rejected')) {
|
||||
console.log('extractVerificationData: Found rejected verification');
|
||||
return { required: true, status: 'rejected' };
|
||||
}
|
||||
|
||||
console.log('extractVerificationData: No verification status found, standard badge');
|
||||
return { required: false, status: null };
|
||||
}
|
||||
|
||||
function createTableRow(record, displayIndex) {
|
||||
const row = document.createElement("tr");
|
||||
row.dataset.recordId = record.id;
|
||||
@@ -675,14 +629,44 @@ function createTableRow(record, displayIndex) {
|
||||
console.log(`=== CREATING ROW ${displayIndex} ===`);
|
||||
console.log(`Employee: ${record.employeeId}`);
|
||||
console.log(`Location accuracy: ${record.location_accuracy}`);
|
||||
console.log(`Verification required: ${record.verification_required}`);
|
||||
console.log(`Verification status: ${record.verification_status}`);
|
||||
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
|
||||
? `<span class="location-accuracy-badge accuracy-${
|
||||
// Create location accuracy badge HTML - check verification status first
|
||||
let locationAccuracyBadge;
|
||||
|
||||
if (record.verification_required && record.verification_status === 'pending') {
|
||||
// Show Review Needed badge for pending verification
|
||||
locationAccuracyBadge = `<span class="location-accuracy-badge badge-review-needed"
|
||||
onclick="openVerificationPhotoModal('${record.id}')"
|
||||
style="cursor: pointer;"
|
||||
title="Click to review verification photo - Distance: ${record.location_accuracy ? record.location_accuracy.toFixed(3) : 'N/A'} miles">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
Review Needed
|
||||
<small>(${record.location_accuracy ? record.location_accuracy.toFixed(3) : 'N/A'} mi)</small>
|
||||
</span>`;
|
||||
} else if (record.verification_status === 'approved') {
|
||||
// Show Verified badge for approved verification
|
||||
locationAccuracyBadge = `<span class="location-accuracy-badge badge-verified"
|
||||
title="Verification approved - Distance: ${record.location_accuracy ? record.location_accuracy.toFixed(3) : 'N/A'} miles">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
Verified
|
||||
<small>(${record.location_accuracy ? record.location_accuracy.toFixed(3) : 'N/A'} mi)</small>
|
||||
</span>`;
|
||||
} else if (record.verification_status === 'rejected') {
|
||||
// Show Rejected badge for rejected verification
|
||||
locationAccuracyBadge = `<span class="location-accuracy-badge badge-rejected"
|
||||
title="Verification rejected - Distance: ${record.location_accuracy ? record.location_accuracy.toFixed(3) : 'N/A'} miles">
|
||||
<i class="fas fa-times-circle"></i>
|
||||
Rejected
|
||||
<small>(${record.location_accuracy ? record.location_accuracy.toFixed(3) : 'N/A'} mi)</small>
|
||||
</span>`;
|
||||
} else if (record.location_accuracy !== null) {
|
||||
// Show standard location accuracy badge
|
||||
locationAccuracyBadge = `<span class="location-accuracy-badge accuracy-${
|
||||
record.accuracy_level
|
||||
}"
|
||||
title="Distance between QR location and check-in location: ${
|
||||
@@ -691,11 +675,14 @@ function createTableRow(record, displayIndex) {
|
||||
<i class="fas fa-ruler"></i>
|
||||
${record.location_accuracy.toFixed(3)} mi
|
||||
<small>(${record.accuracy_level})</small>
|
||||
</span>`
|
||||
: `<span class="location-accuracy-badge accuracy-unknown" title="Location accuracy could not be calculated">
|
||||
</span>`;
|
||||
} else {
|
||||
// No accuracy data
|
||||
locationAccuracyBadge = `<span class="location-accuracy-badge accuracy-unknown" title="Location accuracy could not be calculated">
|
||||
<i class="fas fa-question-circle"></i>
|
||||
Unknown
|
||||
</span>`;
|
||||
}
|
||||
|
||||
// Address display logic based on location accuracy
|
||||
let addressDisplayHTML = "";
|
||||
@@ -849,6 +836,15 @@ function createTableRow(record, displayIndex) {
|
||||
</td>
|
||||
<td>
|
||||
<div class="record-actions">
|
||||
${
|
||||
record.verification_required && record.verification_status === 'pending'
|
||||
? `<button onclick="openVerificationPhotoModal('${record.id}')"
|
||||
class="action-btn btn-review"
|
||||
title="Review Verification Photo">
|
||||
<i class="fas fa-camera"></i>
|
||||
</button>`
|
||||
: ''
|
||||
}
|
||||
${
|
||||
hasEditPermission
|
||||
? `
|
||||
@@ -1181,3 +1177,236 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
|
||||
console.log("Enhanced export functionality initialized");
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// VERIFICATION PHOTO REVIEW FUNCTIONS
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Open verification photo modal for review
|
||||
* @param {string} recordId - The attendance record ID
|
||||
*/
|
||||
function openVerificationPhotoModal(recordId) {
|
||||
console.log(`Opening verification photo modal for record: ${recordId}`);
|
||||
|
||||
const modal = document.getElementById("verificationPhotoModal");
|
||||
const modalBody = document.getElementById("verificationPhotoModalBody");
|
||||
|
||||
if (!modal || !modalBody) {
|
||||
console.error("Verification photo modal elements not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
modalBody.innerHTML = `
|
||||
<div class="verification-loading">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
<p>Loading verification photo...</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.style.display = "block";
|
||||
|
||||
// Fetch record details with verification photo
|
||||
fetch(`/api/attendance/${recordId}/verification-details`)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch verification details");
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
if (!data.success) {
|
||||
throw new Error(data.message || "Failed to load verification data");
|
||||
}
|
||||
|
||||
renderVerificationPhotoModal(data.record);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error loading verification photo:", error);
|
||||
modalBody.innerHTML = `
|
||||
<div class="verification-error">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<p>Error loading verification photo. Please try again.</p>
|
||||
<button onclick="closeVerificationPhotoModal()" class="btn btn-secondary">Close</button>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render verification photo modal content
|
||||
* @param {Object} record - The attendance record with verification data
|
||||
*/
|
||||
function renderVerificationPhotoModal(record) {
|
||||
const modalBody = document.getElementById("verificationPhotoModalBody");
|
||||
|
||||
const content = `
|
||||
<div class="verification-photo-review">
|
||||
<!-- Employee & Date Info -->
|
||||
<div class="verification-header-info">
|
||||
<h3>
|
||||
<i class="fas fa-user"></i>
|
||||
Employee: ${record.employee_id}
|
||||
</h3>
|
||||
<p>
|
||||
<i class="fas fa-calendar"></i>
|
||||
${record.check_in_date} at ${record.check_in_time}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Verification Photo -->
|
||||
<div class="verification-photo-container">
|
||||
${
|
||||
record.verification_photo
|
||||
? `<img src="${record.verification_photo}"
|
||||
alt="Verification Photo"
|
||||
class="verification-photo-large"
|
||||
onclick="window.open(this.src, '_blank')"
|
||||
style="cursor: zoom-in;"
|
||||
title="Click to view full size" />`
|
||||
: `<div class="no-photo">
|
||||
<i class="fas fa-image"></i>
|
||||
<p>No verification photo available</p>
|
||||
</div>`
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Location Details -->
|
||||
<div class="verification-details-grid">
|
||||
<div class="verification-detail-card">
|
||||
<h4>Location Name</h4>
|
||||
<p>${record.location_name}</p>
|
||||
</div>
|
||||
|
||||
<div class="verification-detail-card">
|
||||
<h4>Distance from QR</h4>
|
||||
<p>${parseFloat(record.location_accuracy).toFixed(3)} miles</p>
|
||||
</div>
|
||||
|
||||
<div class="verification-detail-card">
|
||||
<h4>Verification Status</h4>
|
||||
<p>
|
||||
<span class="verification-status-pending">
|
||||
<i class="fas fa-clock"></i>
|
||||
Pending Review
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="verification-detail-card">
|
||||
<h4>Device</h4>
|
||||
<p>${record.device_info || "Unknown"}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Address Information -->
|
||||
<div class="verification-detail-card" style="grid-column: 1 / -1;">
|
||||
<h4>Check-in Address</h4>
|
||||
<p>${record.checked_in_address || "No address recorded"}</p>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="verification-actions">
|
||||
<button onclick="updateVerificationStatus(${
|
||||
record.id
|
||||
}, 'approved')" class="btn-approve">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
Approve Check-in
|
||||
</button>
|
||||
<button onclick="updateVerificationStatus(${
|
||||
record.id
|
||||
}, 'rejected')" class="btn-reject">
|
||||
<i class="fas fa-times-circle"></i>
|
||||
Reject Check-in
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modalBody.innerHTML = content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close verification photo modal
|
||||
*/
|
||||
function closeVerificationPhotoModal() {
|
||||
const modal = document.getElementById("verificationPhotoModal");
|
||||
if (modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update verification status (approve/reject)
|
||||
* @param {number} recordId - The attendance record ID
|
||||
* @param {string} status - The new status ('approved' or 'rejected')
|
||||
*/
|
||||
function updateVerificationStatus(recordId, status) {
|
||||
if (
|
||||
!confirm(
|
||||
`Are you sure you want to ${status} this verification?\n\nThis action will be logged for audit purposes.`
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Updating verification status for record ${recordId} to ${status}`);
|
||||
|
||||
// Show loading state
|
||||
const modalBody = document.getElementById("verificationPhotoModalBody");
|
||||
modalBody.innerHTML = `
|
||||
<div class="verification-loading">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
<p>Updating verification status...</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Send update request
|
||||
fetch(`/verification-review/${recordId}/update`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
status: status,
|
||||
note: `Verification ${status} from attendance report review`,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
alert(
|
||||
`Verification ${status} successfully!\n\nThe page will reload to show the updated status.`
|
||||
);
|
||||
closeVerificationPhotoModal();
|
||||
// Reload the page to show updated status
|
||||
window.location.reload();
|
||||
} else {
|
||||
throw new Error(data.message || "Failed to update verification status");
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error updating verification status:", error);
|
||||
alert(`Error: ${error.message}\n\nPlease try again.`);
|
||||
// Reload modal to show previous state
|
||||
openVerificationPhotoModal(recordId);
|
||||
});
|
||||
}
|
||||
|
||||
// Close verification modal when clicking outside
|
||||
window.addEventListener("click", function (event) {
|
||||
const verificationModal = document.getElementById("verificationPhotoModal");
|
||||
|
||||
if (event.target === verificationModal) {
|
||||
closeVerificationPhotoModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard shortcut for closing verification modal
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.key === "Escape") {
|
||||
closeVerificationPhotoModal();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user