Time Attendance dashboard layout changed

This commit is contained in:
Nguyen Ngo
2025-09-18 17:18:06 -04:00
parent b9d40460f0
commit 8a0f4e8f4b
2 changed files with 353 additions and 311 deletions
+299 -270
View File
@@ -2,30 +2,21 @@
{% block title %}Time Attendance Dashboard - {{ COMPANY_NAME }}{% endblock %}
{% block extra_head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/time_attendance.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/attendance.css') }}">
{% endblock %}
{% block page_title %}Time Attendance{% endblock %}
{% block content %}
<div class="time-attendance-page">
<div class="attendance-page">
<!-- Page Header -->
<div class="time-attendance-header">
<div class="header-navigation">
<a href="{{ url_for('dashboard') }}" class="back-button">
<i class="fas fa-arrow-left"></i>
Back to Dashboard
</a>
</div>
<div class="attendance-header">
<div class="header-content">
<h1>
<i class="fas fa-clock"></i>
Time Attendance Management
Time Attendance Dashboard
</h1>
<p class="header-description">
Import, manage, and analyze employee time attendance data from Excel files
</p>
<p>Manage and analyze employee time attendance data from Excel imports</p>
</div>
<div class="header-actions">
@@ -36,121 +27,203 @@
</a>
{% endif %}
<a href="{{ url_for('time_attendance_records') }}" class="btn btn-secondary">
<i class="fas fa-list"></i>
View Records
<i class="fas fa-search"></i>
View All Records
</a>
</div>
</div>
<!-- Statistics Grid -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon records">
<i class="fas fa-database"></i>
</div>
<div class="stat-info">
<h3>{{ "{:,}".format(total_records) }}</h3>
<p>Total Records</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon employees">
<i class="fas fa-users"></i>
</div>
<div class="stat-info">
<h3>{{ "{:,}".format(unique_employees) }}</h3>
<p>Unique Employees</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon locations">
<i class="fas fa-map-marker-alt"></i>
</div>
<div class="stat-info">
<h3>{{ "{:,}".format(unique_locations) }}</h3>
<p>Locations</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon imports">
<i class="fas fa-file-import"></i>
</div>
<div class="stat-info">
<h3>{{ "{:,}".format(recent_imports|length) }}</h3>
<p>Recent Imports</p>
</div>
</div>
</div>
<!-- Quick Actions Section -->
<div class="import-section">
<div class="import-header">
<h2>
<i class="fas fa-bolt"></i>
Quick Actions
</h2>
</div>
<div class="import-body">
<div class="form-grid">
<div class="action-card">
<h3>
<i class="fas fa-search"></i>
Search Records
</h3>
<p>Find specific attendance records by employee, date, or location</p>
<a href="{{ url_for('time_attendance_records') }}" class="btn btn-primary">
<i class="fas fa-search"></i>
Search Now
</a>
<!-- 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>
{% if session.role == 'admin' %}
<div class="action-card">
<h3>
<i class="fas fa-upload"></i>
Import Excel File
</h3>
<p>Upload and import time attendance data from Excel spreadsheets</p>
<a href="{{ url_for('import_time_attendance') }}" class="btn btn-success">
<i class="fas fa-file-excel"></i>
Import Data
</a>
<div class="stat-content">
<h3>{{ "{:,}".format(total_records) }}</h3>
<p>Total Records</p>
</div>
{% endif %}
<div class="action-card">
<h3>
<i class="fas fa-chart-bar"></i>
Generate Reports
</h3>
<p>Create detailed attendance reports and analytics</p>
<a href="#" class="btn btn-warning">
<i class="fas fa-chart-line"></i>
View Reports
</a>
</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>
<!-- Recent Imports Section -->
<!-- Recent Records Table -->
{% 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>Employee ID</th>
<th>Employee Name</th>
<th>Location</th>
<th>Action</th>
<th>Date</th>
<th>Time</th>
<th>Platform</th>
<th>Address</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for record in recent_records %}
<tr>
<td>{{ loop.index }}</td>
<td>
<div class="employee-info">
<span class="employee-id">{{ record.employee_id }}</span>
</div>
</td>
<td>
<div class="employee-name">
<i class="fas fa-user"></i>
<span>{{ record.employee_name or 'Unknown' }}</span>
</div>
</td>
<td>
<div class="location-info">
<i class="fas fa-map-marker-alt"></i>
{{ record.location_name }}
</div>
</td>
<td>
<div class="action-info">
{% if record.action_description.lower() == 'check in' %}
<i class="fas fa-sign-in-alt" style="color: #059669;"></i>
{% elif record.action_description.lower() == 'check out' %}
<i class="fas fa-sign-out-alt" style="color: #d97706;"></i>
{% else %}
<i class="fas fa-clock" style="color: #6b7280;"></i>
{% endif %}
{{ record.action_description }}
</div>
</td>
<td>
<div class="date-info">
<i class="fas fa-calendar"></i>
{{ record.attendance_date.strftime('%Y-%m-%d') }}
</div>
</td>
<td>
<div class="time-info">
<i class="fas fa-clock"></i>
{{ record.attendance_time.strftime('%H:%M:%S') }}
</div>
</td>
<td>
<div class="platform-info">
{% if record.platform %}
<i class="fas fa-mobile-alt"></i>
{{ record.platform[:25] }}{% if record.platform|length > 25 %}...{% endif %}
{% else %}
<span class="text-muted">-</span>
{% endif %}
</div>
</td>
<td>
<div class="address-info">
{% if record.recorded_address %}
<i class="fas fa-map-pin"></i>
<span title="{{ record.recorded_address }}">
{{ record.recorded_address[:35] }}{% if record.recorded_address|length > 35 %}...{% endif %}
</span>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</div>
</td>
<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="records-section">
<div class="records-header">
<h2>
<div class="attendance-table-section">
<div class="table-header">
<h3>
<i class="fas fa-history"></i>
Recent Imports
</h2>
<div class="records-meta">
Last {{ recent_imports|length }} import batches
Recent Import Batches
</h3>
<div class="table-controls">
<span>Last {{ recent_imports|length }} import batches</span>
</div>
</div>
<div class="records-table-container">
<table class="records-table">
<div class="table-container">
<table class="attendance-table">
<thead>
<tr>
<th>Import Date</th>
@@ -163,29 +236,31 @@
<tbody>
{% for import_batch in recent_imports %}
<tr>
<td class="datetime-cell">
{{ import_batch.import_date.strftime('%Y-%m-%d %H:%M') if import_batch.import_date else 'Unknown' }}
<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 text-success"></i>
<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) }} records
</span>
<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 class="actions-cell">
<a href="{{ url_for('time_attendance_records', import_batch=import_batch.import_batch_id) }}"
class="action-btn view">
<i class="fas fa-eye"></i>
View
</a>
<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>
</div>
</td>
</tr>
{% endfor %}
@@ -195,52 +270,6 @@
</div>
{% endif %}
<!-- Employee Quick Access -->
{% if employees %}
<div class="records-section">
<div class="records-header">
<h2>
<i class="fas fa-users"></i>
Employee Quick Access
</h2>
<div class="records-meta">
Click to view employee attendance records
</div>
</div>
<div class="employee-grid">
{% for employee in employees[:12] %}
<div class="employee-card">
<div class="employee-avatar">
{{ employee.employee_name[0].upper() if employee.employee_name else employee.employee_id[0].upper() }}
</div>
<div class="employee-info">
<div class="employee-name">{{ employee.employee_name }}</div>
<div class="employee-id">ID: {{ employee.employee_id }}</div>
</div>
<a href="{{ url_for('time_attendance_records', employee_id=employee.employee_id) }}"
class="btn btn-sm btn-outline">
<i class="fas fa-eye"></i>
View Records
</a>
</div>
{% endfor %}
{% if employees|length > 12 %}
<div class="employee-card view-all">
<div class="view-all-content">
<i class="fas fa-plus-circle"></i>
<span>{{ employees|length - 12 }} more employees</span>
<a href="{{ url_for('time_attendance_records') }}" class="btn btn-sm btn-primary">
View All
</a>
</div>
</div>
{% endif %}
</div>
</div>
{% endif %}
<!-- Empty State -->
{% if total_records == 0 %}
<div class="empty-state">
@@ -250,156 +279,156 @@
<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 and will automatically process your data.
The system supports various Excel formats (.xlsx, .xls) and will automatically process your data.
</p>
{% if session.role == 'admin' %}
<a href="{{ url_for('import_time_attendance') }}" class="btn btn-primary">
<i class="fas fa-upload"></i>
Import Your First File
</a>
<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>
.action-card {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
padding: 1.5rem;
text-align: center;
transition: all 0.2s ease-in-out;
}
.action-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px 0 rgba(0, 0, 0, 0.1);
}
.action-card h3 {
/* Additional styles specific to this page */
.table-footer {
display: flex;
justify-content: space-between;
align-items: center;
justify-content: center;
gap: 0.5rem;
font-size: 1.125rem;
font-weight: 600;
color: #0f172a;
margin-bottom: 0.5rem;
padding: 1rem 1.5rem;
background: var(--gray-50);
border-top: 1px solid var(--gray-200);
font-size: var(--font-size-sm);
}
.action-card p {
color: #64748b;
font-size: 0.875rem;
margin-bottom: 1.5rem;
}
.source-info {
display: flex;
align-items: center;
gap: 0.5rem;
}
.text-success {
color: #10b981;
.table-info {
color: var(--gray-600);
}
.record-count {
font-weight: 600;
color: #0f172a;
color: var(--gray-900);
}
.batch-id {
background: #f1f5f9;
background: var(--gray-100);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
border-radius: var(--radius);
font-family: monospace;
font-size: 0.75rem;
color: #475569;
font-size: var(--font-size-xs);
color: var(--gray-600);
}
.employee-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
padding: 1.5rem;
.text-muted {
color: var(--gray-400);
font-style: italic;
}
.employee-card {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
padding: 1rem;
.date-info,
.time-info,
.action-info,
.platform-info,
.address-info,
.source-info {
display: flex;
align-items: center;
gap: 1rem;
transition: all 0.2s ease-in-out;
gap: 0.5rem;
font-size: var(--font-size-sm);
}
.employee-card:hover {
background: #ffffff;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.employee-card.view-all {
background: #8b5cf6;
border-color: #8b5cf6;
color: #ffffff;
justify-content: center;
.date-info i,
.time-info i,
.action-info i {
width: 14px;
text-align: center;
flex-direction: column;
gap: 0.5rem;
}
.view-all-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
.platform-info,
.address-info {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.view-all-content i {
font-size: 1.5rem;
margin-bottom: 0.25rem;
.empty-state {
text-align: center;
padding: 4rem 2rem;
background: var(--white);
border-radius: var(--radius-xl);
box-shadow: var(--shadow);
}
.employee-card .employee-avatar {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #8b5cf6, #7c3aed);
.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: #ffffff;
font-size: 0.875rem;
color: var(--gray-500);
font-size: 3rem;
}
.empty-state h3 {
font-size: var(--font-size-2xl);
font-weight: 600;
flex-shrink: 0;
color: var(--gray-900);
margin-bottom: 0.5rem;
}
.employee-card .employee-info {
flex: 1;
.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;
}
.employee-card .employee-name {
font-weight: 600;
color: #0f172a;
margin-bottom: 0.125rem;
}
.employee-card .employee-id {
color: #64748b;
font-size: 0.75rem;
.empty-actions {
margin-top: 1rem;
}
@media (max-width: 768px) {
.employee-grid {
grid-template-columns: 1fr;
.table-footer {
flex-direction: column;
gap: 1rem;
text-align: center;
}
.form-grid {
grid-template-columns: 1fr;
/* 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;
}
}
</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();
}
}
</script>
{% endblock %}