Update attendance report functions: edit & delete record for admin & payroll users
This commit is contained in:
@@ -3240,6 +3240,150 @@ def attendance_report():
|
|||||||
flash('Error loading attendance report. Please check the server logs for details.', 'error')
|
flash('Error loading attendance report. Please check the server logs for details.', 'error')
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
|
@app.route('/attendance/<int:record_id>/edit', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
@log_database_operations('attendance_update')
|
||||||
|
def edit_attendance(record_id):
|
||||||
|
"""Edit attendance record (Admin and Payroll only)"""
|
||||||
|
# Check if user has permission to edit attendance records
|
||||||
|
if session.get('role') not in ['admin', 'payroll']:
|
||||||
|
flash('Access denied. Only administrators and payroll staff can edit attendance records.', 'error')
|
||||||
|
return redirect(url_for('attendance_report'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
attendance_record = AttendanceData.query.get_or_404(record_id)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
# Track changes for logging
|
||||||
|
changes = {}
|
||||||
|
old_values = {
|
||||||
|
'employee_id': attendance_record.employee_id,
|
||||||
|
'check_in_date': attendance_record.check_in_date,
|
||||||
|
'check_in_time': attendance_record.check_in_time,
|
||||||
|
'location_name': attendance_record.location_name
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update attendance record fields
|
||||||
|
new_employee_id = request.form['employee_id'].strip().upper()
|
||||||
|
new_check_in_date = datetime.strptime(request.form['check_in_date'], '%Y-%m-%d').date()
|
||||||
|
new_check_in_time = datetime.strptime(request.form['check_in_time'], '%H:%M').time()
|
||||||
|
new_location_name = request.form['location_name'].strip()
|
||||||
|
|
||||||
|
# Track what changed
|
||||||
|
if attendance_record.employee_id != new_employee_id:
|
||||||
|
changes['employee_id'] = f"{attendance_record.employee_id} → {new_employee_id}"
|
||||||
|
if attendance_record.check_in_date != new_check_in_date:
|
||||||
|
changes['check_in_date'] = f"{attendance_record.check_in_date} → {new_check_in_date}"
|
||||||
|
if attendance_record.check_in_time != new_check_in_time:
|
||||||
|
changes['check_in_time'] = f"{attendance_record.check_in_time} → {new_check_in_time}"
|
||||||
|
if attendance_record.location_name != new_location_name:
|
||||||
|
changes['location_name'] = f"{attendance_record.location_name} → {new_location_name}"
|
||||||
|
|
||||||
|
# Apply changes
|
||||||
|
attendance_record.employee_id = new_employee_id
|
||||||
|
attendance_record.check_in_date = new_check_in_date
|
||||||
|
attendance_record.check_in_time = new_check_in_time
|
||||||
|
attendance_record.location_name = new_location_name
|
||||||
|
attendance_record.updated_timestamp = datetime.utcnow()
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Log the successful update
|
||||||
|
if changes:
|
||||||
|
logger_handler.log_security_event(
|
||||||
|
event_type="attendance_record_update",
|
||||||
|
description=f"{session.get('role', 'unknown').title()} {session.get('username')} updated attendance record {record_id}",
|
||||||
|
severity="MEDIUM",
|
||||||
|
additional_data={'record_id': record_id, 'changes': changes, 'user_role': session.get('role')}
|
||||||
|
)
|
||||||
|
print(f"[LOG] {session.get('role', 'unknown').title()} {session.get('username')} updated attendance record {record_id}: {changes}")
|
||||||
|
|
||||||
|
flash(f'Attendance record for {new_employee_id} updated successfully!', 'success')
|
||||||
|
return redirect(url_for('attendance_report'))
|
||||||
|
|
||||||
|
# GET request - show edit form
|
||||||
|
# Get available QR codes for location dropdown
|
||||||
|
qr_codes = QRCode.query.filter_by(active_status=True).all()
|
||||||
|
|
||||||
|
return render_template('edit_attendance.html',
|
||||||
|
attendance_record=attendance_record,
|
||||||
|
qr_codes=qr_codes)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
|
logger_handler.log_database_error('attendance_update', e)
|
||||||
|
print(f"[LOG] Error updating attendance record {record_id}: {e}")
|
||||||
|
flash('Error updating attendance record. Please try again.', 'error')
|
||||||
|
return redirect(url_for('attendance_report'))
|
||||||
|
|
||||||
|
@app.route('/attendance/<int:record_id>/delete', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
@log_database_operations('attendance_delete')
|
||||||
|
def delete_attendance(record_id):
|
||||||
|
"""Delete attendance record (Admin and Payroll only)"""
|
||||||
|
# Check if user has permission to delete attendance records
|
||||||
|
if session.get('role') not in ['admin', 'payroll']:
|
||||||
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': 'Access denied. Only administrators and payroll staff can delete attendance records.'
|
||||||
|
}), 403
|
||||||
|
else:
|
||||||
|
flash('Access denied. Only administrators and payroll staff can delete attendance records.', 'error')
|
||||||
|
return redirect(url_for('attendance_report'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
attendance_record = AttendanceData.query.get_or_404(record_id)
|
||||||
|
|
||||||
|
# Store record info for logging before deletion
|
||||||
|
employee_id = attendance_record.employee_id
|
||||||
|
location_name = attendance_record.location_name
|
||||||
|
check_in_date = attendance_record.check_in_date
|
||||||
|
|
||||||
|
# Log the deletion
|
||||||
|
logger_handler.log_security_event(
|
||||||
|
event_type="attendance_record_deletion",
|
||||||
|
description=f"{session.get('role', 'unknown').title()} {session.get('username')} deleted attendance record {record_id}",
|
||||||
|
severity="HIGH",
|
||||||
|
additional_data={
|
||||||
|
'record_id': record_id,
|
||||||
|
'employee_id': employee_id,
|
||||||
|
'location_name': location_name,
|
||||||
|
'check_in_date': str(check_in_date),
|
||||||
|
'user_role': session.get('role')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Delete the record
|
||||||
|
db.session.delete(attendance_record)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
print(f"[LOG] {session.get('role', 'unknown').title()} {session.get('username')} deleted attendance record {record_id} for employee {employee_id}")
|
||||||
|
|
||||||
|
# Return JSON response for AJAX requests
|
||||||
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': f'Attendance record for {employee_id} deleted successfully!'
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
flash(f'Attendance record for {employee_id} deleted successfully!', 'success')
|
||||||
|
return redirect(url_for('attendance_report'))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
|
logger_handler.log_database_error('attendance_delete', e)
|
||||||
|
print(f"[LOG] Error deleting attendance record {record_id}: {e}")
|
||||||
|
|
||||||
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': 'Error deleting attendance record. Please try again.'
|
||||||
|
}), 500
|
||||||
|
else:
|
||||||
|
flash('Error deleting attendance record. Please try again.', 'error')
|
||||||
|
return redirect(url_for('attendance_report'))
|
||||||
|
|
||||||
@app.route('/api/attendance/stats')
|
@app.route('/api/attendance/stats')
|
||||||
@admin_required
|
@admin_required
|
||||||
def attendance_stats_api():
|
def attendance_stats_api():
|
||||||
|
|||||||
@@ -1284,3 +1284,71 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* Delete button styling */
|
||||||
|
.btn-delete {
|
||||||
|
background: var(--danger-light);
|
||||||
|
color: var(--danger-color);
|
||||||
|
border: 1px solid rgba(220, 38, 38, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete:hover {
|
||||||
|
background: var(--danger-color);
|
||||||
|
color: var(--white);
|
||||||
|
border-color: var(--danger-color);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(220, 38, 38, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced action buttons container */
|
||||||
|
.record-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-2);
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Action button improvements */
|
||||||
|
.action-btn {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form styling for edit page */
|
||||||
|
.form-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.form-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -379,11 +379,26 @@ function createTableRow(record, displayIndex) {
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="record-actions">
|
<div class="record-actions">
|
||||||
|
${
|
||||||
|
hasEditPermission
|
||||||
|
? `
|
||||||
<button onclick="editRecord('${record.id}')"
|
<button onclick="editRecord('${record.id}')"
|
||||||
class="action-btn btn-edit"
|
class="action-btn btn-edit"
|
||||||
title="Edit Record">
|
title="Edit Record">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</button>
|
</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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
@@ -511,9 +526,72 @@ function updateFilterStats() {
|
|||||||
|
|
||||||
// Enhanced record actions
|
// Enhanced record actions
|
||||||
function editRecord(recordId) {
|
function editRecord(recordId) {
|
||||||
|
// Check permissions before allowing edit
|
||||||
|
if (!hasEditPermission) {
|
||||||
|
alert(
|
||||||
|
"Access denied. Only administrators and payroll staff can edit attendance records."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`Edit record: ${recordId}`);
|
console.log(`Edit record: ${recordId}`);
|
||||||
// Implement edit functionality
|
// Log the action
|
||||||
alert("Edit functionality to be implemented");
|
console.log(`[LOG] User attempting to edit attendance record: ${recordId}`);
|
||||||
|
|
||||||
|
// Redirect to edit page
|
||||||
|
window.location.href = `/attendance/${recordId}/edit`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteRecord(recordId, employeeId) {
|
||||||
|
// Check permissions before allowing delete
|
||||||
|
if (!hasEditPermission) {
|
||||||
|
alert(
|
||||||
|
"Access denied. Only administrators and payroll staff can delete attendance records."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Delete record: ${recordId}`);
|
||||||
|
|
||||||
|
// Confirmation dialog
|
||||||
|
const confirmMessage = `Are you sure you want to delete the attendance record for employee "${employeeId}"?\n\nThis action cannot be undone.`;
|
||||||
|
|
||||||
|
if (confirm(confirmMessage)) {
|
||||||
|
console.log(
|
||||||
|
`[LOG] User confirmed deletion of attendance record: ${recordId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Send delete request
|
||||||
|
fetch(`/attendance/${recordId}/delete`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data.success) {
|
||||||
|
console.log(
|
||||||
|
`[LOG] Successfully deleted attendance record: ${recordId}`
|
||||||
|
);
|
||||||
|
alert("Attendance record deleted successfully!");
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
`[LOG] Failed to delete attendance record: ${recordId} - ${data.message}`
|
||||||
|
);
|
||||||
|
alert(data.message || "Error deleting record. Please try again.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(
|
||||||
|
`[LOG] Error during attendance record deletion: ${recordId}`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
alert("Error deleting record. Please try again.");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
@@ -696,11 +774,26 @@ function createTableRow(record, displayIndex) {
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="record-actions">
|
<div class="record-actions">
|
||||||
|
${
|
||||||
|
hasEditPermission
|
||||||
|
? `
|
||||||
<button onclick="editRecord('${record.id}')"
|
<button onclick="editRecord('${record.id}')"
|
||||||
class="action-btn btn-edit"
|
class="action-btn btn-edit"
|
||||||
title="Edit Record">
|
title="Edit Record">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</button>
|
</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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -266,11 +266,22 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="record-actions">
|
<div class="record-actions">
|
||||||
|
{% if session.role in ['admin', 'payroll'] %}
|
||||||
<button onclick="editRecord('{{ record.id }}')"
|
<button onclick="editRecord('{{ record.id }}')"
|
||||||
class="action-btn btn-edit"
|
class="action-btn btn-edit"
|
||||||
title="Edit Record">
|
title="Edit Record">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button onclick="deleteRecord('{{ record.id }}', '{{ record.employee_id }}')"
|
||||||
|
class="action-btn btn-delete"
|
||||||
|
title="Delete Record">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted" title="Admin or Payroll access required">
|
||||||
|
<i class="fas fa-lock"></i>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -353,6 +364,14 @@
|
|||||||
{% block extra_scripts %}
|
{% block extra_scripts %}
|
||||||
<script src="{{ url_for('static', filename='js/attendance_report.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/attendance_report.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
|
// Template variables (processed by Flask)
|
||||||
|
const userRole = '{{ session.role }}';
|
||||||
|
const hasEditPermission = ['admin', 'payroll'].includes(userRole);
|
||||||
|
|
||||||
|
// Debug logging
|
||||||
|
console.log('User Role:', userRole);
|
||||||
|
console.log('Has Edit Permission:', hasEditPermission);
|
||||||
|
|
||||||
// Enhanced JavaScript for new functionality
|
// Enhanced JavaScript for new functionality
|
||||||
const hasLocationAccuracy = {{ 'true' if has_location_accuracy_feature else 'false' }};
|
const hasLocationAccuracy = {{ 'true' if has_location_accuracy_feature else 'false' }};
|
||||||
|
|
||||||
@@ -443,44 +462,12 @@ function viewRecordDetails(recordId) {
|
|||||||
${record.accuracy_level.toUpperCase()}
|
${record.accuracy_level.toUpperCase()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-item">
|
|
||||||
<strong>QR Address:</strong>
|
|
||||||
<span>${record.qr_address}</span>
|
|
||||||
</div>
|
|
||||||
<div class="detail-item">
|
|
||||||
<strong>Check-in Address:</strong>
|
|
||||||
<span>${record.checked_in_address}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else if (record.gps_accuracy) {
|
|
||||||
accuracySection = `
|
|
||||||
<div class="detail-section">
|
|
||||||
<h4><i class="fas fa-crosshairs"></i> GPS Information</h4>
|
|
||||||
<div class="detail-item">
|
|
||||||
<strong>GPS Accuracy:</strong>
|
|
||||||
<span>${record.gps_accuracy}m</span>
|
|
||||||
</div>
|
|
||||||
<div class="detail-item">
|
|
||||||
<strong>Coordinates:</strong>
|
|
||||||
<span>${record.coordinates}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
accuracySection = `
|
|
||||||
<div class="detail-section">
|
|
||||||
<h4><i class="fas fa-question-circle"></i> Location Information</h4>
|
|
||||||
<div class="detail-item">
|
|
||||||
<strong>Status:</strong>
|
|
||||||
<span>No location data available</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
modalBody.innerHTML = `
|
modalBody.innerHTML = `
|
||||||
<div class="record-details-grid">
|
<div class="record-details">
|
||||||
<div class="detail-section">
|
<div class="detail-section">
|
||||||
<h4><i class="fas fa-user"></i> Employee Information</h4>
|
<h4><i class="fas fa-user"></i> Employee Information</h4>
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
@@ -520,9 +507,67 @@ function viewRecordDetails(recordId) {
|
|||||||
modal.style.display = 'block';
|
modal.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OVERRIDE EDIT/DELETE FUNCTIONS WITH PROPER PERMISSION CHECKING
|
||||||
function editRecord(recordId) {
|
function editRecord(recordId) {
|
||||||
console.log(`Edit record: ${recordId}`);
|
console.log('Edit function called for record:', recordId);
|
||||||
alert('Edit functionality to be implemented');
|
console.log('User role:', userRole);
|
||||||
|
console.log('Has permission:', hasEditPermission);
|
||||||
|
|
||||||
|
// Check permissions before allowing edit
|
||||||
|
if (!hasEditPermission) {
|
||||||
|
alert('Access denied. Only administrators and payroll staff can edit attendance records.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[LOG] User attempting to edit attendance record: ${recordId}`);
|
||||||
|
|
||||||
|
// Redirect to edit page
|
||||||
|
window.location.href = `/attendance/${recordId}/edit`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteRecord(recordId, employeeId) {
|
||||||
|
console.log('Delete function called for record:', recordId);
|
||||||
|
console.log('User role:', userRole);
|
||||||
|
console.log('Has permission:', hasEditPermission);
|
||||||
|
|
||||||
|
// Check permissions before allowing delete
|
||||||
|
if (!hasEditPermission) {
|
||||||
|
alert('Access denied. Only administrators and payroll staff can delete attendance records.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Delete record: ${recordId}`);
|
||||||
|
|
||||||
|
// Confirmation dialog
|
||||||
|
const confirmMessage = `Are you sure you want to delete the attendance record for employee "${employeeId}"?\n\nThis action cannot be undone.`;
|
||||||
|
|
||||||
|
if (confirm(confirmMessage)) {
|
||||||
|
console.log(`[LOG] User confirmed deletion of attendance record: ${recordId}`);
|
||||||
|
|
||||||
|
// Send delete request
|
||||||
|
fetch(`/attendance/${recordId}/delete`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
console.log(`[LOG] Successfully deleted attendance record: ${recordId}`);
|
||||||
|
alert('Attendance record deleted successfully!');
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
console.error(`[LOG] Failed to delete attendance record: ${recordId} - ${data.message}`);
|
||||||
|
alert(data.message || 'Error deleting record. Please try again.');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`[LOG] Error during attendance record deletion: ${recordId}`, error);
|
||||||
|
alert('Error deleting record. Please try again.');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close modals when clicking outside
|
// Close modals when clicking outside
|
||||||
@@ -545,5 +590,40 @@ document.addEventListener('keydown', function(event) {
|
|||||||
closeMapModal();
|
closeMapModal();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Force update buttons on page load for admin/payroll users
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('Page loaded. User role:', userRole, 'Has edit permission:', hasEditPermission);
|
||||||
|
|
||||||
|
if (hasEditPermission) {
|
||||||
|
// Find all record action containers and update them
|
||||||
|
const recordActions = document.querySelectorAll('.record-actions');
|
||||||
|
recordActions.forEach(function(actionDiv) {
|
||||||
|
// Check if it contains a lock icon (meaning user is locked out)
|
||||||
|
const lockIcon = actionDiv.querySelector('.fa-lock');
|
||||||
|
if (lockIcon) {
|
||||||
|
const recordRow = actionDiv.closest('tr');
|
||||||
|
const recordId = recordRow.dataset.recordId;
|
||||||
|
const employeeCell = recordRow.querySelector('.employee-id');
|
||||||
|
const employeeId = employeeCell ? employeeCell.textContent.trim() : 'Unknown';
|
||||||
|
|
||||||
|
// Replace lock icon with edit/delete buttons
|
||||||
|
actionDiv.innerHTML = `
|
||||||
|
<button onclick="editRecord('${recordId}')"
|
||||||
|
class="action-btn btn-edit"
|
||||||
|
title="Edit Record">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<button onclick="deleteRecord('${recordId}', '${employeeId}')"
|
||||||
|
class="action-btn btn-delete"
|
||||||
|
title="Delete Record">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('✅ Admin/Payroll edit/delete buttons have been activated');
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
{% extends "base_authenticated.html" %} {% block title %}Edit Attendance Record
|
||||||
|
- QR Code Management{% endblock %} {% block extra_head %}
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="{{ url_for('static', filename='css/forms.css') }}"
|
||||||
|
/>
|
||||||
|
<style>
|
||||||
|
.edit-attendance-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 2rem;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 2px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-header h1 {
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-header p {
|
||||||
|
color: #6b7280;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section h3 {
|
||||||
|
color: #374151;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-info {
|
||||||
|
background: #f9fafb;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-info h4 {
|
||||||
|
color: #374151;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-info p {
|
||||||
|
color: #6b7280;
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %} {% block content %}
|
||||||
|
<div class="edit-attendance-container">
|
||||||
|
<div class="form-header">
|
||||||
|
<h1>
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
Edit Attendance Record
|
||||||
|
</h1>
|
||||||
|
<p>Modify attendance record details (Admin & Payroll Access)</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Current Record Info -->
|
||||||
|
<div class="record-info">
|
||||||
|
<h4><i class="fas fa-info-circle"></i> Current Record Information</h4>
|
||||||
|
<p><strong>Record ID:</strong> {{ attendance_record.id }}</p>
|
||||||
|
<p>
|
||||||
|
<strong>Current Employee:</strong> {{ attendance_record.employee_id }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Current Date:</strong> {{
|
||||||
|
attendance_record.check_in_date.strftime('%Y-%m-%d') }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Current Time:</strong> {{
|
||||||
|
attendance_record.check_in_time.strftime('%H:%M') }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Current Location:</strong> {{ attendance_record.location_name }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form
|
||||||
|
method="POST"
|
||||||
|
action="{{ url_for('edit_attendance', record_id=attendance_record.id) }}"
|
||||||
|
>
|
||||||
|
<!-- Employee Information -->
|
||||||
|
<div class="form-section">
|
||||||
|
<h3><i class="fas fa-user"></i> Employee Information</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="employee_id">Employee ID *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="employee_id"
|
||||||
|
name="employee_id"
|
||||||
|
value="{{ attendance_record.employee_id }}"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Enter employee ID"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Date & Time Information -->
|
||||||
|
<div class="form-section">
|
||||||
|
<h3><i class="fas fa-clock"></i> Check-in Details</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="check_in_date">Check-in Date *</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
id="check_in_date"
|
||||||
|
name="check_in_date"
|
||||||
|
value="{{ attendance_record.check_in_date.strftime('%Y-%m-%d') }}"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="check_in_time">Check-in Time *</label>
|
||||||
|
<input
|
||||||
|
type="time"
|
||||||
|
id="check_in_time"
|
||||||
|
name="check_in_time"
|
||||||
|
value="{{ attendance_record.check_in_time.strftime('%H:%M') }}"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Location Information -->
|
||||||
|
<div class="form-section">
|
||||||
|
<h3><i class="fas fa-map-marker-alt"></i> Location Information</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="location_name">Location Name *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="location_name"
|
||||||
|
name="location_name"
|
||||||
|
value="{{ attendance_record.location_name }}"
|
||||||
|
required
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Enter location name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Form Actions -->
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save"></i>
|
||||||
|
Update Record
|
||||||
|
</button>
|
||||||
|
<a href="{{ url_for('attendance_report') }}" class="btn btn-secondary">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
Cancel
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %} {% block extra_scripts %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
// Form validation
|
||||||
|
const form = document.querySelector("form");
|
||||||
|
form.addEventListener("submit", function (e) {
|
||||||
|
const employeeId = document.getElementById("employee_id").value.trim();
|
||||||
|
const locationName = document
|
||||||
|
.getElementById("location_name")
|
||||||
|
.value.trim();
|
||||||
|
|
||||||
|
if (!employeeId || !locationName) {
|
||||||
|
e.preventDefault();
|
||||||
|
alert("Please fill in all required fields.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm update
|
||||||
|
const confirmUpdate = confirm(
|
||||||
|
"Are you sure you want to update this attendance record?"
|
||||||
|
);
|
||||||
|
if (!confirmUpdate) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-uppercase employee ID
|
||||||
|
document
|
||||||
|
.getElementById("employee_id")
|
||||||
|
.addEventListener("input", function () {
|
||||||
|
this.value = this.value.toUpperCase();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user