Update verification photo pages
This commit is contained in:
@@ -5348,9 +5348,12 @@ def verification_review():
|
|||||||
status_filter = request.args.get('status', 'pending')
|
status_filter = request.args.get('status', 'pending')
|
||||||
date_from = request.args.get('date_from', '')
|
date_from = request.args.get('date_from', '')
|
||||||
date_to = request.args.get('date_to', '')
|
date_to = request.args.get('date_to', '')
|
||||||
|
project_filter = request.args.get('project', '')
|
||||||
|
location_filter = request.args.get('location', '')
|
||||||
|
employee_filter = request.args.get('employee', '')
|
||||||
|
|
||||||
# Build query
|
# Build query - join with QRCode to access project_id
|
||||||
query = AttendanceData.query.filter(
|
query = AttendanceData.query.join(QRCode).filter(
|
||||||
AttendanceData.verification_required == True
|
AttendanceData.verification_required == True
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -5363,11 +5366,54 @@ def verification_review():
|
|||||||
if date_to:
|
if date_to:
|
||||||
query = query.filter(AttendanceData.check_in_date <= date_to)
|
query = query.filter(AttendanceData.check_in_date <= date_to)
|
||||||
|
|
||||||
|
# Apply project filter
|
||||||
|
if project_filter:
|
||||||
|
try:
|
||||||
|
query = query.filter(QRCode.project_id == int(project_filter))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Apply location filter
|
||||||
|
if location_filter:
|
||||||
|
query = query.filter(AttendanceData.location_name.ilike(f'%{location_filter}%'))
|
||||||
|
|
||||||
|
# Apply employee ID filter
|
||||||
|
if employee_filter:
|
||||||
|
query = query.filter(AttendanceData.employee_id.ilike(f'%{employee_filter}%'))
|
||||||
|
|
||||||
# Get records with QR code information
|
# Get records with QR code information
|
||||||
verifications = query.join(QRCode).order_by(
|
verifications = query.order_by(
|
||||||
AttendanceData.verification_timestamp.desc()
|
AttendanceData.verification_timestamp.desc()
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
|
# Build a dictionary for employee names lookup
|
||||||
|
employee_names = {}
|
||||||
|
for record in verifications:
|
||||||
|
if record.employee_id and record.employee_id not in employee_names:
|
||||||
|
try:
|
||||||
|
employee = Employee.query.filter_by(id=int(record.employee_id)).first()
|
||||||
|
if employee:
|
||||||
|
employee_names[record.employee_id] = f"{employee.lastName}, {employee.firstName}"
|
||||||
|
else:
|
||||||
|
employee_names[record.employee_id] = None
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
employee_names[record.employee_id] = None
|
||||||
|
|
||||||
|
# Build a dictionary for project names lookup
|
||||||
|
project_names = {}
|
||||||
|
for record in verifications:
|
||||||
|
if record.qr_code and record.qr_code.project_id:
|
||||||
|
project_id = record.qr_code.project_id
|
||||||
|
if project_id not in project_names:
|
||||||
|
try:
|
||||||
|
project = Project.query.get(project_id)
|
||||||
|
if project:
|
||||||
|
project_names[project_id] = project.name
|
||||||
|
else:
|
||||||
|
project_names[project_id] = None
|
||||||
|
except Exception:
|
||||||
|
project_names[project_id] = None
|
||||||
|
|
||||||
# Get counts for status badges
|
# Get counts for status badges
|
||||||
pending_count = AttendanceData.query.filter(
|
pending_count = AttendanceData.query.filter(
|
||||||
AttendanceData.verification_status == 'pending'
|
AttendanceData.verification_status == 'pending'
|
||||||
@@ -5381,6 +5427,20 @@ def verification_review():
|
|||||||
AttendanceData.verification_status == 'rejected'
|
AttendanceData.verification_status == 'rejected'
|
||||||
).count()
|
).count()
|
||||||
|
|
||||||
|
# Get all projects for filter dropdown
|
||||||
|
projects = Project.query.filter_by(active_status=True).order_by(Project.name).all()
|
||||||
|
|
||||||
|
# Get unique locations for filter dropdown
|
||||||
|
locations = db.session.query(AttendanceData.location_name).filter(
|
||||||
|
AttendanceData.verification_required == True
|
||||||
|
).distinct().order_by(AttendanceData.location_name).all()
|
||||||
|
location_list = [loc[0] for loc in locations if loc[0]]
|
||||||
|
|
||||||
|
# Log access
|
||||||
|
logger_handler.logger.info(
|
||||||
|
f"User {session.get('username')} ({session.get('role')}) accessed verification review page"
|
||||||
|
)
|
||||||
|
|
||||||
return render_template('verification_review.html',
|
return render_template('verification_review.html',
|
||||||
verifications=verifications,
|
verifications=verifications,
|
||||||
pending_count=pending_count,
|
pending_count=pending_count,
|
||||||
@@ -5388,7 +5448,14 @@ def verification_review():
|
|||||||
rejected_count=rejected_count,
|
rejected_count=rejected_count,
|
||||||
status_filter=status_filter,
|
status_filter=status_filter,
|
||||||
date_from=date_from,
|
date_from=date_from,
|
||||||
date_to=date_to)
|
date_to=date_to,
|
||||||
|
project_filter=project_filter,
|
||||||
|
location_filter=location_filter,
|
||||||
|
employee_filter=employee_filter,
|
||||||
|
projects=projects,
|
||||||
|
locations=location_list,
|
||||||
|
employee_names=employee_names,
|
||||||
|
project_names=project_names)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger_handler.logger.error(f"Error in verification review: {e}")
|
logger_handler.logger.error(f"Error in verification review: {e}")
|
||||||
|
|||||||
@@ -60,6 +60,36 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-group">
|
||||||
|
<label for="project">Project</label>
|
||||||
|
<select name="project" id="project" class="filter-select">
|
||||||
|
<option value="">All Projects</option>
|
||||||
|
{% for project in projects %}
|
||||||
|
<option value="{{ project.id }}" {% if project_filter == project.id|string %}selected{% endif %}>
|
||||||
|
{{ project.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-group">
|
||||||
|
<label for="location">Location</label>
|
||||||
|
<select name="location" id="location" class="filter-select">
|
||||||
|
<option value="">All Locations</option>
|
||||||
|
{% for location in locations %}
|
||||||
|
<option value="{{ location }}" {% if location_filter == location %}selected{% endif %}>
|
||||||
|
{{ location }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-group">
|
||||||
|
<label for="employee">Employee ID</label>
|
||||||
|
<input type="text" name="employee" id="employee" value="{{ employee_filter }}"
|
||||||
|
class="filter-input" placeholder="Search by ID...">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<label for="date_from">From Date</label>
|
<label for="date_from">From Date</label>
|
||||||
<input type="date" name="date_from" id="date_from" value="{{ date_from }}" class="filter-input">
|
<input type="date" name="date_from" id="date_from" value="{{ date_from }}" class="filter-input">
|
||||||
@@ -70,10 +100,13 @@
|
|||||||
<input type="date" name="date_to" id="date_to" value="{{ date_to }}" class="filter-input">
|
<input type="date" name="date_to" id="date_to" value="{{ date_to }}" class="filter-input">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-group">
|
<div class="filter-group filter-buttons">
|
||||||
<button type="submit" class="btn-filter">
|
<button type="submit" class="btn-filter">
|
||||||
<i class="fas fa-filter"></i> Apply Filters
|
<i class="fas fa-filter"></i> Apply Filters
|
||||||
</button>
|
</button>
|
||||||
|
<a href="{{ url_for('verification_review') }}" class="btn-reset">
|
||||||
|
<i class="fas fa-undo"></i> Reset
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -89,7 +122,10 @@
|
|||||||
<div class="verification-info">
|
<div class="verification-info">
|
||||||
<h3>
|
<h3>
|
||||||
<i class="fas fa-user"></i>
|
<i class="fas fa-user"></i>
|
||||||
Employee {{ record.employee_id }}
|
{{ record.employee_id }}
|
||||||
|
{% if employee_names.get(record.employee_id) %}
|
||||||
|
<span class="employee-name">- {{ employee_names.get(record.employee_id) }}</span>
|
||||||
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="verification-meta">
|
<div class="verification-meta">
|
||||||
<span class="meta-item">
|
<span class="meta-item">
|
||||||
@@ -104,7 +140,21 @@
|
|||||||
<i class="fas fa-map-marker-alt"></i>
|
<i class="fas fa-map-marker-alt"></i>
|
||||||
{{ record.location_name }}
|
{{ record.location_name }}
|
||||||
</span>
|
</span>
|
||||||
|
{% if record.qr_code and record.qr_code.location_event %}
|
||||||
|
<span class="meta-item event-badge {{ 'check-in' if record.qr_code.location_event == 'Check In' else 'check-out' }}">
|
||||||
|
<i class="fas fa-{{ 'sign-in-alt' if record.qr_code.location_event == 'Check In' else 'sign-out-alt' }}"></i>
|
||||||
|
{{ record.qr_code.location_event }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if record.qr_code and record.qr_code.project_id and project_names.get(record.qr_code.project_id) %}
|
||||||
|
<div class="verification-project">
|
||||||
|
<span class="project-badge">
|
||||||
|
<i class="fas fa-folder"></i>
|
||||||
|
{{ project_names.get(record.qr_code.project_id) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="verification-status">
|
<div class="verification-status">
|
||||||
{% if record.verification_status == 'pending' %}
|
{% if record.verification_status == 'pending' %}
|
||||||
@@ -330,6 +380,71 @@
|
|||||||
background: #2563eb;
|
background: #2563eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-reset {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.625rem 1.5rem;
|
||||||
|
background: #6b7280;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
text-decoration: none;
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-reset:hover {
|
||||||
|
background: #4b5563;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.employee-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #4b5563;
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verification-project {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.375rem;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
background: #e0e7ff;
|
||||||
|
color: #3730a3;
|
||||||
|
border-radius: 9999px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-badge {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-badge.check-in {
|
||||||
|
background: #d1fae5;
|
||||||
|
color: #065f46;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-badge.check-out {
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
|
|
||||||
.verifications-grid {
|
.verifications-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ QR Code Management{% endblock %} {% block extra_head %}
|
|||||||
<div class="info-row">
|
<div class="info-row">
|
||||||
<span class="info-label">QR Address</span>
|
<span class="info-label">QR Address</span>
|
||||||
<span class="info-value"
|
<span class="info-value"
|
||||||
>{{ qr_code.address if qr_code else 'N/A' }}</span
|
>{{ qr_code.location_address if qr_code else 'N/A' }}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-row">
|
<div class="info-row">
|
||||||
|
|||||||
Reference in New Issue
Block a user