Files
GOV_QR_Codes_Management/templates/time_attendance_dashboard.html
T

551 lines
15 KiB
HTML

{% extends "base_authenticated.html" %}
{% block title %}Time Attendance Dashboard - {{ COMPANY_NAME }}{% endblock %}
{% block extra_head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/attendance.css') }}">
{% endblock %}
{% block content %}
<div class="attendance-page">
<!-- Page Header -->
<div class="attendance-header">
<div class="header-content">
<h1>
<i class="fas fa-clock"></i>
Time Attendance Dashboard
</h1>
<p>Manage and analyze employee time attendance data from Excel imports</p>
</div>
<div class="header-actions">
{% if session.role in ['admin', 'payroll'] %}
<a href="{{ url_for('import_time_attendance') }}" class="btn btn-primary">
<i class="fas fa-upload"></i>
Import Excel Data
</a>
{% endif %}
<a href="{{ url_for('time_attendance_records') }}" class="btn btn-secondary">
<i class="fas fa-search"></i>
View All Records
</a>
</div>
</div>
<!-- Statistics Section -->
<div class="stats-section">
<div class="stats-grid">
<div class="stat-card primary">
<div class="stat-icon">
<i class="fas fa-database"></i>
</div>
<div class="stat-content">
<h3>{{ "{:,}".format(total_records) }}</h3>
<p>Total Records</p>
</div>
</div>
<div class="stat-card success">
<div class="stat-icon">
<i class="fas fa-users"></i>
</div>
<div class="stat-content">
<h3>{{ "{:,}".format(unique_employees) }}</h3>
<p>Employees</p>
</div>
</div>
<div class="stat-card info">
<div class="stat-icon">
<i class="fas fa-map-marker-alt"></i>
</div>
<div class="stat-content">
<h3>{{ "{:,}".format(unique_locations) }}</h3>
<p>Locations</p>
</div>
</div>
<div class="stat-card warning">
<div class="stat-icon">
<i class="fas fa-file-import"></i>
</div>
<div class="stat-content">
<h3>{{ "{:,}".format(recent_imports|length) }}</h3>
<p>Import Batches</p>
</div>
</div>
</div>
</div>
{% if recent_records %}
<div class="attendance-table-section">
<div class="table-header">
<h3>
<i class="fas fa-table"></i>
Recent Time Attendance Records
</h3>
<div class="table-controls">
<div class="entries-per-page">
<span>Showing latest {{ recent_records|length }} records</span>
</div>
</div>
</div>
<div class="table-container">
<table class="attendance-table">
<thead>
<tr>
<th>#</th>
<th>ID</th>
<th>Name</th>
<th>Platform</th>
<th>Date</th>
<th>Time</th>
<th>Location Name</th>
<th>Action Description</th>
<th>Event Description</th>
<th>Recorded Address</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for record in recent_records %}
<tr>
<!-- # -->
<td>{{ loop.index }}</td>
<!-- 1. ID (Employee ID) -->
<td>
<div class="employee-info">
<span class="employee-id">{{ record.employee_id }}</span>
</div>
</td>
<!-- 2. Name -->
<td>
<div class="employee-name-simple">
{{ record.employee_name or 'Unknown' }}
</div>
</td>
<!-- 3. Platform -->
<td class="platform-cell">
{{ record.platform or 'Unknown' }}
</td>
<!-- 4. Date -->
<td class="date-cell">
{{ record.attendance_date.strftime('%Y-%m-%d') }}
</td>
<!-- 5. Time -->
<td class="time-cell">
{{ record.attendance_time.strftime('%H:%M:%S') }}
</td>
<!-- 6. Location Name -->
<td>
<div class="location-info">
<i class="fas fa-map-marker-alt"></i>
{{ record.location_name }}
</div>
</td>
<!-- 7. Action Description -->
<td>
<span class="action-badge {{ record.action_description.lower().replace(' ', '-') }}">
{% if record.action_description.lower() == 'check in' %}
<i class="fas fa-sign-in-alt"></i>
{% elif record.action_description.lower() == 'check out' %}
<i class="fas fa-sign-out-alt"></i>
{% else %}
<i class="fas fa-clock"></i>
{% endif %}
{{ record.action_description }}
</span>
</td>
<!-- 8. Event Description -->
<td class="event-cell">
{{ record.event_description if record.event_description else '-' }}
</td>
<!-- 9. Recorded Address -->
<td class="address-cell">
{% if record.recorded_address %}
<span title="{{ record.recorded_address }}">
{{ record.recorded_address[:35] }}{% if record.recorded_address|length > 35 %}...{% endif %}
</span>
{% else %}
<span class="text-muted">No address</span>
{% endif %}
</td>
<!-- Actions -->
<td>
<div class="record-actions">
<button class="action-btn btn-view" onclick="viewRecord({{ record.id }})" title="View Details">
<i class="fas fa-eye"></i>
</button>
{% if session.role == 'admin' %}
<button class="action-btn btn-delete" onclick="deleteRecord({{ record.id }}, '{{ record.employee_name }}', '{{ record.attendance_date }}')" title="Delete">
<i class="fas fa-trash"></i>
</button>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Table Footer -->
<div class="table-footer">
<div class="table-info">
<span>Showing {{ recent_records|length }} of {{ "{:,}".format(total_records) }} total records</span>
</div>
<div class="table-actions">
<a href="{{ url_for('time_attendance_records') }}" class="btn btn-primary">
<i class="fas fa-list"></i>
View All Records
</a>
</div>
</div>
</div>
{% endif %}
<!-- Recent Imports Table -->
{% if recent_imports %}
<div class="attendance-table-section">
<div class="table-header">
<h3>
<i class="fas fa-history"></i>
Recent Import Batches
</h3>
<div class="table-controls">
<span>Last {{ recent_imports|length }} import batches</span>
</div>
</div>
<div class="table-container">
<table class="attendance-table">
<thead>
<tr>
<th>Import Date</th>
<th>Source</th>
<th>Records</th>
<th>Batch ID</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for import_batch in recent_imports %}
<tr>
<td>
<div class="date-info">
<i class="fas fa-calendar"></i>
{{ import_batch.import_date.strftime('%Y-%m-%d %H:%M') if import_batch.import_date else 'Unknown' }}
</div>
</td>
<td>
<div class="source-info">
<i class="fas fa-file-excel" style="color: #059669;"></i>
{{ import_batch.import_source or 'Excel Import' }}
</div>
</td>
<td>
<span class="record-count">{{ "{:,}".format(import_batch.record_count) }}</span>
</td>
<td>
<code class="batch-id">{{ import_batch.import_batch_id[:8] }}...</code>
</td>
<td>
<div class="record-actions">
<a href="{{ url_for('time_attendance_records', import_batch=import_batch.import_batch_id) }}"
class="action-btn btn-view" title="View Records">
<i class="fas fa-eye"></i>
</a>
<a href="{{ url_for('view_import_batch', batch_id=import_batch.import_batch_id) }}"
class="action-btn btn-info" title="Batch Details">
<i class="fas fa-info-circle"></i>
</a>
{% if session.role == 'admin' %}
<button onclick="deleteBatch('{{ import_batch.import_batch_id }}')"
class="action-btn btn-delete" title="Delete Batch">
<i class="fas fa-trash"></i>
</button>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<!-- Empty State -->
{% if total_records == 0 %}
<div class="empty-state">
<div class="empty-icon">
<i class="fas fa-clock"></i>
</div>
<h3>No Time Attendance Data</h3>
<p>
Get started by importing your first Excel file with time attendance data.
The system supports various Excel formats (.xlsx, .xls) and will automatically process your data.
</p>
{% if session.role == 'admin' %}
<div class="empty-actions">
<a href="{{ url_for('import_time_attendance') }}" class="btn btn-primary">
<i class="fas fa-upload"></i>
Import Your First File
</a>
</div>
{% endif %}
</div>
{% endif %}
</div>
<style>
/* Additional styles specific to this page */
.table-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
background: var(--gray-50);
border-top: 1px solid var(--gray-200);
font-size: var(--font-size-sm);
}
.table-info {
color: var(--gray-600);
}
.record-count {
font-weight: 600;
color: var(--gray-900);
}
.batch-id {
background: var(--gray-100);
padding: 0.25rem 0.5rem;
border-radius: var(--radius);
font-family: monospace;
font-size: var(--font-size-xs);
color: var(--gray-600);
}
.text-muted {
color: var(--gray-400);
font-style: italic;
}
.date-info,
.time-info,
.action-info,
.platform-info,
.address-info,
.source-info {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: var(--font-size-sm);
}
.date-info i,
.time-info i,
.action-info i {
width: 14px;
text-align: center;
}
.platform-info,
.address-info {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.empty-state {
text-align: center;
padding: 4rem 2rem;
background: var(--white);
border-radius: var(--radius-xl);
box-shadow: var(--shadow);
}
.empty-icon {
width: 120px;
height: 120px;
margin: 0 auto 1.5rem;
background: var(--gray-100);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: var(--gray-500);
font-size: 3rem;
}
.empty-state h3 {
font-size: var(--font-size-2xl);
font-weight: 600;
color: var(--gray-900);
margin-bottom: 0.5rem;
}
.empty-state p {
color: var(--gray-600);
font-size: var(--font-size-lg);
margin-bottom: 2rem;
max-width: 500px;
margin-left: auto;
margin-right: auto;
line-height: 1.6;
}
.empty-actions {
margin-top: 1rem;
}
@media (max-width: 768px) {
.table-footer {
flex-direction: column;
gap: 1rem;
text-align: center;
}
/* Hide some columns on mobile */
.attendance-table th:nth-child(8),
.attendance-table td:nth-child(8),
.attendance-table th:nth-child(9),
.attendance-table td:nth-child(9) {
display: none;
}
}
.employee-name-simple {
font-weight: 500;
color: #0f172a;
}
.date-cell,
.time-cell {
font-family: 'Courier New', monospace;
color: #374151;
font-size: 0.875rem;
}
.event-cell {
color: #64748b;
font-size: 0.875rem;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.address-cell {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.875rem;
}
.text-muted {
color: #94a3b8;
font-style: italic;
font-size: 0.875rem;
}
.platform-cell {
color: #64748b;
font-size: 0.875rem;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Responsive adjustments for dashboard table */
@media (max-width: 1400px) {
.attendance-table th:nth-child(4),
.attendance-table td:nth-child(4),
.attendance-table th:nth-child(9),
.attendance-table td:nth-child(9) {
display: none;
}
}
@media (max-width: 1024px) {
.attendance-table th:nth-child(4),
.attendance-table td:nth-child(4),
.attendance-table th:nth-child(7),
.attendance-table td:nth-child(7),
.attendance-table th:nth-child(9),
.attendance-table td:nth-child(9),
.attendance-table th:nth-child(10),
.attendance-table td:nth-child(10) {
display: none;
}
}
@media (max-width: 768px) {
.attendance-table th:nth-child(4),
.attendance-table td:nth-child(4),
.attendance-table th:nth-child(7),
.attendance-table td:nth-child(7),
.attendance-table th:nth-child(8),
.attendance-table td:nth-child(8),
.attendance-table th:nth-child(9),
.attendance-table td:nth-child(9),
.attendance-table th:nth-child(10),
.attendance-table td:nth-child(10) {
display: none;
}
}
</style>
<script>
// View record details
function viewRecord(recordId) {
window.location.href = "{{ url_for('time_attendance_record_detail', record_id=0) }}".replace('0', recordId);
}
// Delete record with confirmation
function deleteRecord(recordId, employeeName, date) {
if (confirm(`Are you sure you want to delete the attendance record for ${employeeName} on ${date}?`)) {
const form = document.createElement('form');
form.method = 'POST';
form.action = "{{ url_for('delete_time_attendance_record', record_id=0) }}".replace('0', recordId);
document.body.appendChild(form);
form.submit();
}
}
function deleteBatch(batchId) {
if (confirm('Are you sure you want to delete this entire import batch? This action cannot be undone.')) {
const form = document.createElement('form');
form.method = 'POST';
form.action = `/time-attendance/import/batch/${batchId}/delete`;
const csrfToken = document.querySelector('meta[name="csrf-token"]');
if (csrfToken) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'csrf_token';
input.value = csrfToken.content;
form.appendChild(input);
}
document.body.appendChild(form);
form.submit();
}
}
</script>
{% endblock %}