Initial Codes
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Attendance Report - QR Code Management{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="attendance-page">
|
||||
<div class="attendance-header">
|
||||
<div class="header-content">
|
||||
<h1>
|
||||
<i class="fas fa-chart-line"></i>
|
||||
Attendance Report
|
||||
</h1>
|
||||
<p>Monitor and analyze staff attendance across all locations</p>
|
||||
</div>
|
||||
|
||||
<div class="header-actions">
|
||||
<button onclick="exportAttendance()" class="btn btn-success">
|
||||
<i class="fas fa-download"></i>
|
||||
Export Data
|
||||
</button>
|
||||
<button onclick="refreshReport()" class="btn btn-secondary">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistics Cards -->
|
||||
<div class="stats-section">
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card primary">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-user-check"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<h3>{{ stats.total_checkins or 0 }}</h3>
|
||||
<p>Total Check-ins</p>
|
||||
<span class="stat-trend">All time</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card success">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-users"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<h3>{{ stats.unique_employees or 0 }}</h3>
|
||||
<p>Unique Employees</p>
|
||||
<span class="stat-trend">Have checked in</span>
|
||||
</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>{{ stats.active_locations or 0 }}</h3>
|
||||
<p>Active Locations</p>
|
||||
<span class="stat-trend">With check-ins</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card warning">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-calendar-day"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<h3>{{ stats.today_checkins or 0 }}</h3>
|
||||
<p>Today's Check-ins</p>
|
||||
<span class="stat-trend">{{ "now"|strftime('%B %d') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters Section -->
|
||||
<div class="filters-section">
|
||||
<div class="filters-card">
|
||||
<div class="filters-header">
|
||||
<h3>
|
||||
<i class="fas fa-filter"></i>
|
||||
Filter Records
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<form method="GET" class="filters-form" id="filtersForm">
|
||||
<div class="filter-row">
|
||||
<div class="filter-group">
|
||||
<label for="date">
|
||||
<i class="fas fa-calendar"></i>
|
||||
Date
|
||||
</label>
|
||||
<input type="date"
|
||||
id="date"
|
||||
name="date"
|
||||
value="{{ date_filter }}"
|
||||
max="{{ 'now'|strftime('%Y-%m-%d') }}">
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label for="location">
|
||||
<i class="fas fa-building"></i>
|
||||
Location
|
||||
</label>
|
||||
<select id="location" name="location">
|
||||
<option value="">All Locations</option>
|
||||
{% for location in locations %}
|
||||
<option value="{{ location }}"
|
||||
{{ 'selected' if location == location_filter else '' }}>
|
||||
{{ location }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label for="employee">
|
||||
<i class="fas fa-id-badge"></i>
|
||||
Employee ID
|
||||
</label>
|
||||
<input type="text"
|
||||
id="employee"
|
||||
name="employee"
|
||||
value="{{ employee_filter }}"
|
||||
placeholder="Enter Employee ID">
|
||||
</div>
|
||||
|
||||
<div class="filter-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-search"></i>
|
||||
Apply Filters
|
||||
</button>
|
||||
<button type="button" onclick="clearFilters()" class="btn btn-outline">
|
||||
<i class="fas fa-times"></i>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Attendance Table -->
|
||||
<div class="attendance-table-section">
|
||||
<div class="table-header">
|
||||
<h3>
|
||||
<i class="fas fa-list"></i>
|
||||
Attendance Records
|
||||
</h3>
|
||||
<div class="table-controls">
|
||||
<div class="entries-per-page">
|
||||
<label>Show:</label>
|
||||
<select id="entriesPerPage" onchange="changeEntriesPerPage()">
|
||||
<option value="25">25</option>
|
||||
<option value="50" selected>50</option>
|
||||
<option value="100">100</option>
|
||||
<option value="all">All</option>
|
||||
</select>
|
||||
<span>entries</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
{% if attendance_records %}
|
||||
<table class="attendance-table" id="attendanceTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th onclick="sortTable(0)">#</th>
|
||||
<th onclick="sortTable(1)">Employee ID <i class="fas fa-sort"></i></th>
|
||||
<th onclick="sortTable(2)">Location <i class="fas fa-sort"></i></th>
|
||||
<th onclick="sortTable(3)">Event <i class="fas fa-sort"></i></th>
|
||||
<th onclick="sortTable(4)">Date <i class="fas fa-sort"></i></th>
|
||||
<th onclick="sortTable(5)">Time <i class="fas fa-sort"></i></th>
|
||||
<th onclick="sortTable(6)">Device <i class="fas fa-sort"></i></th>
|
||||
<th onclick="sortTable(7)">Status <i class="fas fa-sort"></i></th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for record in attendance_records %}
|
||||
<tr data-record-id="{{ record.id }}">
|
||||
<td>{{ loop.index }}</td>
|
||||
<td>
|
||||
<div class="employee-info">
|
||||
<span class="employee-id">{{ record.employee_id }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="location-info">
|
||||
<i class="fas fa-map-marker-alt"></i>
|
||||
{{ record.location_name }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="event-info">
|
||||
{{ record.location_event }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="date-info">
|
||||
{{ record.check_in_date.strftime('%Y-%m-%d') }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="time-info">
|
||||
{{ record.check_in_time.strftime('%H:%M') }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="device-info">
|
||||
<i class="fas fa-mobile-alt"></i>
|
||||
<span title="{{ record.device_info }}">
|
||||
{{ record.device_info[:20] }}{% if record.device_info|length > 20 %}...{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="status-badge {{ record.status }}">
|
||||
<i class="fas {{ 'fa-check-circle' if record.status == 'present' else 'fa-times-circle' }}"></i>
|
||||
{{ record.status.title() }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="record-actions">
|
||||
<button onclick="viewRecordDetails({{ record.id }})"
|
||||
class="action-btn btn-view"
|
||||
title="View Details">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
<button onclick="editRecord({{ record.id }})"
|
||||
class="action-btn btn-edit"
|
||||
title="Edit Record">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button onclick="deleteRecord({{ record.id }})"
|
||||
class="action-btn btn-delete"
|
||||
title="Delete Record">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">
|
||||
<i class="fas fa-clipboard-list"></i>
|
||||
</div>
|
||||
<h3>No Attendance Records Found</h3>
|
||||
<p>{% if date_filter or location_filter or employee_filter %}
|
||||
No records match your current filters. Try adjusting the filter criteria.
|
||||
{% else %}
|
||||
No staff have checked in yet. QR codes need to be scanned to generate attendance data.
|
||||
{% endif %}</p>
|
||||
<button onclick="clearFilters()" class="btn btn-primary">
|
||||
<i class="fas fa-refresh"></i>
|
||||
Clear Filters
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="pagination-container" id="paginationContainer">
|
||||
<!-- Pagination will be dynamically generated -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Section -->
|
||||
<div class="charts-section">
|
||||
<div class="charts-grid">
|
||||
<div class="chart-card">
|
||||
<div class="chart-header">
|
||||
<h3>
|
||||
<i class="fas fa-chart-bar"></i>
|
||||
Daily Check-ins (Last 7 Days)
|
||||
</h3>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="dailyChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-card">
|
||||
<div class="chart-header">
|
||||
<h3>
|
||||
<i class="fas fa-chart-pie"></i>
|
||||
Check-ins by Location
|
||||
</h3>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="locationChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Record Details Modal -->
|
||||
<div id="recordModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 id="modalTitle">Attendance Record Details</h3>
|
||||
<button class="modal-close" onclick="closeRecordModal()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" id="modalBody">
|
||||
<!-- Dynamic content will be loaded here -->
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button onclick="closeRecordModal()" class="btn btn-secondary">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/attendance_report.js') }}"></script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user