Time Attendance

This commit is contained in:
Nguyen Ngo
2025-09-17 14:46:51 -04:00
parent 7933fcc24f
commit 89ef6bf709
8 changed files with 4697 additions and 0 deletions
+405
View File
@@ -0,0 +1,405 @@
{% extends "base_authenticated.html" %}
{% block title %}Time Attendance Dashboard - {{ COMPANY_NAME }}{% endblock %}
{% block extra_head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/time_attendance.css') }}">
{% endblock %}
{% block page_title %}Time Attendance{% endblock %}
{% block content %}
<div class="time-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="header-content">
<h1>
<i class="fas fa-clock"></i>
Time Attendance Management
</h1>
<p class="header-description">
Import, manage, and analyze employee time attendance data from Excel files
</p>
</div>
<div class="header-actions">
{% if session.role == 'admin' %}
<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-list"></i>
View 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>
</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>
{% 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>
</div>
</div>
<!-- Recent Imports Section -->
{% if recent_imports %}
<div class="records-section">
<div class="records-header">
<h2>
<i class="fas fa-history"></i>
Recent Imports
</h2>
<div class="records-meta">
Last {{ recent_imports|length }} import batches
</div>
</div>
<div class="records-table-container">
<table class="records-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 class="datetime-cell">
{{ import_batch.import_date.strftime('%Y-%m-%d %H:%M') if import_batch.import_date else 'Unknown' }}
</td>
<td>
<div class="source-info">
<i class="fas fa-file-excel text-success"></i>
{{ import_batch.import_source or 'Excel Import' }}
</div>
</td>
<td>
<span class="record-count">
{{ "{:,}".format(import_batch.record_count) }} records
</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>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</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">
<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 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>
{% 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 {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
font-size: 1.125rem;
font-weight: 600;
color: #0f172a;
margin-bottom: 0.5rem;
}
.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;
}
.record-count {
font-weight: 600;
color: #0f172a;
}
.batch-id {
background: #f1f5f9;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-family: monospace;
font-size: 0.75rem;
color: #475569;
}
.employee-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
padding: 1.5rem;
}
.employee-card {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
padding: 1rem;
display: flex;
align-items: center;
gap: 1rem;
transition: all 0.2s ease-in-out;
}
.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;
text-align: center;
flex-direction: column;
gap: 0.5rem;
}
.view-all-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.view-all-content i {
font-size: 1.5rem;
margin-bottom: 0.25rem;
}
.employee-card .employee-avatar {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #8b5cf6, #7c3aed);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 0.875rem;
font-weight: 600;
flex-shrink: 0;
}
.employee-card .employee-info {
flex: 1;
}
.employee-card .employee-name {
font-weight: 600;
color: #0f172a;
margin-bottom: 0.125rem;
}
.employee-card .employee-id {
color: #64748b;
font-size: 0.75rem;
}
@media (max-width: 768px) {
.employee-grid {
grid-template-columns: 1fr;
}
.form-grid {
grid-template-columns: 1fr;
}
}
</style>
{% endblock %}