Update dashboard

This commit is contained in:
2025-09-01 13:20:52 -04:00
parent f3dbaf2b81
commit 22dd08754f
4 changed files with 300 additions and 16 deletions
+63
View File
@@ -3792,6 +3792,52 @@ def toggle_qr_status(qr_id):
'message': 'Error updating QR code status. Please try again.'
}), 500
@app.route('/qr-codes/<int:qr_id>/copy-url', methods=['POST'])
@login_required
def copy_qr_url(qr_id):
"""Log QR code URL copy action"""
try:
qr_code = QRCode.query.get_or_404(qr_id)
# Log URL copy action
logger_handler.logger.info(f"User {session.get('username', 'unknown')} copied URL for QR code {qr_code.name} (ID: {qr_id})")
return jsonify({
'success': True,
'message': f'QR code URL copied to clipboard!',
'url': f"{request.url_root}qr/{qr_code.qr_url}"
})
except Exception as e:
logger_handler.logger.error(f"Error copying QR URL for ID {qr_id}: {e}")
return jsonify({
'success': False,
'message': 'Error copying QR code URL.'
}), 500
@app.route('/qr-codes/<int:qr_id>/open-link', methods=['POST'])
@login_required
def open_qr_link(qr_id):
"""Log QR code link open action"""
try:
qr_code = QRCode.query.get_or_404(qr_id)
# Log link open action
logger_handler.logger.info(f"User {session.get('username', 'unknown')} opened link for QR code {qr_code.name} (ID: {qr_id})")
return jsonify({
'success': True,
'message': f'Opening QR code link...',
'url': f"{request.url_root}qr/{qr_code.qr_url}"
})
except Exception as e:
logger_handler.logger.error(f"Error opening QR link for ID {qr_id}: {e}")
return jsonify({
'success': False,
'message': 'Error opening QR code link.'
}), 500
@app.route('/qr-codes/<int:qr_id>/activate', methods=['POST'])
@login_required
def activate_qr_code(qr_id):
@@ -5512,6 +5558,16 @@ def get_employee_name(employee_id):
print(f"⚠️ Error getting employee name for ID {employee_id}: {e}")
return f"Employee {employee_id}"
def get_qr_code_checkin_count(qr_code_id):
"""Helper function to get total check-ins count for a QR code"""
try:
count = AttendanceData.query.filter_by(qr_code_id=qr_code_id).count()
logger_handler.logger.info(f"QR Code {qr_code_id} total check-ins: {count}")
return count
except Exception as e:
logger_handler.logger.error(f"Error getting check-ins count for QR {qr_code_id}: {e}")
return 0
@app.context_processor
def inject_payroll_utils():
"""Inject payroll utility functions into templates"""
@@ -5520,6 +5576,13 @@ def inject_payroll_utils():
'format_hours': lambda hours: f"{hours:.2f}" if hours else "0.00"
}
@app.context_processor
def inject_dashboard_utils():
"""Inject dashboard utility functions into templates"""
return {
'get_qr_code_checkin_count': get_qr_code_checkin_count
}
@app.route('/statistics')
@login_required
def qr_statistics():
+67
View File
@@ -1270,3 +1270,70 @@
max-width: calc(100vw - 2rem);
}
}
/* Enhanced QR Action Buttons */
.qr-action-btn.copy {
background: linear-gradient(135deg, #10b981, #059669);
color: white;
}
.qr-action-btn.copy:hover {
background: linear-gradient(135deg, #059669, #047857);
transform: translateY(-1px);
}
.qr-action-btn.link {
background: linear-gradient(135deg, #3b82f6, #2563eb);
color: white;
}
.qr-action-btn.link:hover {
background: linear-gradient(135deg, #2563eb, #1d4ed8);
transform: translateY(-1px);
}
/* Check-ins Counter Styling */
.qr-checkins-count {
background: rgba(59, 130, 246, 0.1);
border: 1px solid rgba(59, 130, 246, 0.2);
border-radius: var(--radius-md);
padding: 0.25rem 0.5rem;
font-weight: 500;
color: #1e40af;
}
.qr-checkins-count i {
color: #3b82f6;
}
/* Responsive Action Buttons */
@media (max-width: 768px) {
.qr-actions-compact {
flex-wrap: wrap;
gap: 0.25rem;
}
.qr-action-btn {
min-width: 32px;
height: 32px;
font-size: 0.75rem;
}
}
/* Enhanced QR Actions Layout */
.qr-actions-compact {
display: flex;
gap: 0.5rem;
align-items: center;
flex-wrap: wrap;
}
.qr-card-actions {
display: flex;
gap: 0.5rem;
justify-content: center;
flex-wrap: wrap;
padding: var(--spacing-3);
border-top: 1px solid var(--gray-200);
background: var(--gray-50);
}
+118 -10
View File
@@ -188,10 +188,13 @@ class DashboardManager {
try {
// Show loading state
const deleteBtn = document.querySelector(`[onclick*="deleteQRCode(${qrId}"]`);
const deleteBtn = document.querySelector(
`[onclick*="deleteQRCode(${qrId}"]`
);
if (deleteBtn) {
deleteBtn.disabled = true;
deleteBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Deleting...';
deleteBtn.innerHTML =
'<i class="fas fa-spinner fa-spin"></i> Deleting...';
}
const response = await fetch(`/qr-codes/${qrId}/delete`, {
@@ -216,16 +219,16 @@ class DashboardManager {
// Use simple alert instead of problematic showToast
alert(`QR code "${qrName}" deleted successfully!`);
} else {
throw new Error(`Server error: ${response.status}`);
}
} catch (error) {
console.error("Delete error:", error);
// Restore button if there was an error
const deleteBtn = document.querySelector(`[onclick*="deleteQRCode(${qrId}"]`);
const deleteBtn = document.querySelector(
`[onclick*="deleteQRCode(${qrId}"]`
);
if (deleteBtn) {
deleteBtn.disabled = false;
deleteBtn.innerHTML = '<i class="fas fa-trash"></i>';
@@ -236,7 +239,6 @@ class DashboardManager {
}
}
// Show custom delete confirmation dialog
showDeleteConfirmation(qrName) {
return new Promise((resolve) => {
@@ -299,19 +301,26 @@ class DashboardManager {
async bulkDeleteQRCodes() {
if (this.selectedQRCodes.size === 0) return;
const confirmed = await this.showBulkDeleteConfirmation(this.selectedQRCodes.size);
const confirmed = await this.showBulkDeleteConfirmation(
this.selectedQRCodes.size
);
if (!confirmed) return;
const deletePromises = Array.from(this.selectedQRCodes).map(qrId => {
const deletePromises = Array.from(this.selectedQRCodes).map((qrId) => {
const qrItem = document.querySelector(`[data-qr-id="${qrId}"]`);
const qrName = qrItem ? qrItem.querySelector('.qr-name')?.textContent || 'Unknown' : 'Unknown';
const qrName = qrItem
? qrItem.querySelector(".qr-name")?.textContent || "Unknown"
: "Unknown";
return this.deleteQRCode(qrId, qrName);
});
try {
await Promise.all(deletePromises);
this.selectedQRCodes.clear();
window.showToast(`Successfully deleted ${deletePromises.length} QR codes`, "success");
window.showToast(
`Successfully deleted ${deletePromises.length} QR codes`,
"success"
);
} catch (error) {
console.error("Bulk delete error:", error);
window.showToast("Some QR codes could not be deleted", "error");
@@ -437,6 +446,96 @@ class DashboardManager {
counter.textContent = `${qrItems.length} results`;
}
}
showToast(message, type = "info") {
// Create toast if showToast doesn't exist globally
if (typeof window.showToast === "function") {
window.showToast(message, type);
} else {
// Fallback to console or simple alert
console.log(`${type.toUpperCase()}: ${message}`);
// Or use a simple notification
const toast = document.createElement("div");
toast.className = `toast toast-${type}`;
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${
type === "success"
? "#10b981"
: type === "error"
? "#ef4444"
: "#3b82f6"
};
color: white;
padding: 12px 16px;
border-radius: 8px;
z-index: 9999;
font-weight: 500;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
`;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.opacity = "0";
toast.style.transform = "translateX(100%)";
setTimeout(() => toast.remove(), 300);
}, 3000);
}
}
// Copy QR Code URL
async copyQRUrl(qrId) {
try {
const response = await fetch(`/qr-codes/${qrId}/copy-url`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
if (data.success && data.url) {
// Copy to clipboard
await navigator.clipboard.writeText(data.url);
this.showToast("QR code URL copied to clipboard!", "success");
} else {
this.showToast("Failed to copy URL", "error");
}
} catch (error) {
console.error("Copy URL error:", error);
this.showToast("Failed to copy URL", "error");
}
}
// Open QR Code Link
async openQRLink(qrId) {
try {
const response = await fetch(`/qr-codes/${qrId}/open-link`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
if (data.success && data.url) {
// Open in new tab
window.open(data.url, "_blank");
this.showToast("QR code link opened!", "success");
} else {
this.showToast("Failed to open link", "error");
}
} catch (error) {
console.error("Open link error:", error);
this.showToast("Failed to open link", "error");
}
}
}
// Initialize dashboard when DOM is loaded
@@ -455,3 +554,12 @@ document.addEventListener("DOMContentLoaded", function () {
window.copyQRData = (name, location, address, event) =>
window.dashboardManager.copyQRData(name, location, address, event);
});
// Global functions for new features
function copyQRUrl(qrId) {
window.dashboardManager?.copyQRUrl(qrId);
}
function openQRLink(qrId) {
window.dashboardManager?.openQRLink(qrId);
}
+48 -2
View File
@@ -1121,7 +1121,14 @@ Management{% endblock %} {% block extra_head %}
<i class="fas fa-home"></i>
<span>{{ qr.location_address }}</span>
</div>
{% endif %} {% if qr.qr_url %}
{% endif %}
<div class="qr-detail-item qr-checkins-count">
<i class="fas fa-users"></i>
<span>{{ get_qr_code_checkin_count(qr.id) }} check-ins</span>
</div>
{% if qr.qr_url %}
<div class="qr-detail-item">
<i class="fas fa-link"></i>
<span>{{ qr.qr_url }}</span>
@@ -1140,6 +1147,22 @@ Management{% endblock %} {% block extra_head %}
</div>
<div class="qr-card-actions">
<button
class="qr-action-btn copy"
onclick="event.stopPropagation(); copyQRUrl({{ qr.id }})"
title="Copy QR Code URL"
>
<i class="fas fa-copy"></i>
</button>
<button
class="qr-action-btn link"
onclick="event.stopPropagation(); openQRLink({{ qr.id }})"
title="Open QR Code Link"
>
<i class="fas fa-external-link-alt"></i>
</button>
<button
class="qr-action-btn download"
onclick="event.stopPropagation(); downloadQRFromCard(this)"
@@ -1243,7 +1266,14 @@ Management{% endblock %} {% block extra_head %}
<i class="fas fa-home"></i>
<span>{{ qr.location_address }}</span>
</div>
{% endif %} {% if qr.qr_url %}
{% endif %}
<div class="qr-detail-item qr-checkins-count">
<i class="fas fa-users"></i>
<span>{{ get_qr_code_checkin_count(qr.id) }} check-ins</span>
</div>
{% if qr.qr_url %}
<div class="qr-detail-item">
<i class="fas fa-link"></i>
<span class="qr-url-short"
@@ -1267,6 +1297,22 @@ Management{% endblock %} {% block extra_head %}
</div>
<div class="qr-actions-compact">
<button
class="qr-action-btn copy"
onclick="event.stopPropagation(); copyQRUrl({{ qr.id }})"
title="Copy QR Code URL"
>
<i class="fas fa-copy"></i>
</button>
<button
class="qr-action-btn link"
onclick="event.stopPropagation(); openQRLink({{ qr.id }})"
title="Open QR Code Link"
>
<i class="fas fa-external-link-alt"></i>
</button>
<button
class="qr-action-btn download"
onclick="event.stopPropagation(); downloadQRFromCard(this)"