From 8b3e80f4858187a0fb4b778cef6e61c1367a8b49 Mon Sep 17 00:00:00 2001 From: NguyenND Date: Fri, 6 Mar 2026 17:49:36 -0500 Subject: [PATCH] Mar 06 2026: fixed time attendance records page, update search by id --- app.py | 23 +++- templates/time_attendance_records.html | 182 +++++++++++++++++++++++-- 2 files changed, 190 insertions(+), 15 deletions(-) diff --git a/app.py b/app.py index a43d409..01f48ee 100644 --- a/app.py +++ b/app.py @@ -10948,13 +10948,32 @@ def time_attendance_records(): unique_employees = TimeAttendance.get_unique_employees() unique_locations = TimeAttendance.get_unique_locations() projects = Project.query.filter_by(active_status=True).order_by(Project.name).all() - + + # Resolve display name for the employee filter input + employee_display_name = '' + if employee_filter: + try: + import re as _re + numeric_only = _re.search(r'\d+', str(employee_filter)) + if numeric_only: + emp = Employee.query.filter_by(id=int(numeric_only.group(0))).first() + if emp: + employee_display_name = f"{emp.lastName}, {emp.firstName}" + else: + employee_display_name = f"ID: {employee_filter}" + else: + employee_display_name = f"ID: {employee_filter}" + except (ValueError, TypeError): + employee_display_name = employee_filter + return render_template( 'time_attendance_records.html', records=records, unique_employees=unique_employees, unique_locations=unique_locations, - projects=projects + projects=projects, + employee_display_name=employee_display_name, + employee_filter=employee_filter or '' ) except Exception as e: diff --git a/templates/time_attendance_records.html b/templates/time_attendance_records.html index 2b49093..72b6a34 100644 --- a/templates/time_attendance_records.html +++ b/templates/time_attendance_records.html @@ -3,6 +3,53 @@ {% block extra_head %} + {% endblock %} {% block content %} @@ -55,15 +102,28 @@
- - + +
+ + +
+ {% if employee_filter %} + + {% endif %} +
@@ -346,8 +406,8 @@ function confirmDelete(recordId, employeeName, date) { } function exportRecords(format) { - // Get current filter values - const employeeId = document.querySelector('select[name="employee_id"]')?.value || ''; + // Get current filter values — employee_id is now a hidden field (autocomplete widget) + const employeeId = document.getElementById('employee_id')?.value || ''; const locationName = document.querySelector('select[name="location_name"]')?.value || ''; const projectId = document.querySelector('select[name="project_id"]')?.value || ''; const startDate = document.querySelector('input[name="start_date"]')?.value || ''; @@ -367,8 +427,8 @@ function exportRecords(format) { } function exportByBuilding() { - // Get current filter values - const employeeId = document.querySelector('select[name="employee_id"]')?.value || ''; + // Get current filter values — employee_id is now a hidden field (autocomplete widget) + const employeeId = document.getElementById('employee_id')?.value || ''; const locationName = document.querySelector('select[name="location_name"]')?.value || ''; const projectId = document.querySelector('select[name="project_id"]')?.value || ''; const startDate = document.querySelector('input[name="start_date"]')?.value || ''; @@ -412,6 +472,102 @@ document.addEventListener('DOMContentLoaded', function() { // Always ensure the filter section is visible filterBody.style.display = 'block'; }); + +// ── Employee autocomplete ────────────────────────────────────────────────── +(function () { + const searchInput = document.getElementById('ta_employee_search'); + const hiddenInput = document.getElementById('employee_id'); + const dropdown = document.getElementById('ta_autocomplete_results'); + let debounceTimer; + + if (!searchInput) return; + + searchInput.addEventListener('input', function () { + const term = this.value.trim(); + + // Keep hidden field in sync when typing a plain numeric ID so the user + // can submit without selecting from the dropdown (handles IDs not in Employee table). + if (/^\d+$/.test(term)) { + hiddenInput.value = term; + } else { + hiddenInput.value = ''; + } + + if (term.length === 0) { + hiddenInput.value = ''; + dropdown.classList.remove('show'); + return; + } + + if (term.length < 2) { + dropdown.classList.remove('show'); + return; + } + + clearTimeout(debounceTimer); + debounceTimer = setTimeout(function () { + fetch('/api/search_employees?q=' + encodeURIComponent(term)) + .then(function (r) { return r.json(); }) + .then(function (data) { renderDropdown(data.employees || []); }) + .catch(function (err) { console.error('Employee search error:', err); }); + }, 300); + }); + + document.addEventListener('click', function (e) { + if (!e.target.closest('.ta-autocomplete-container')) { + dropdown.classList.remove('show'); + } + }); + + function renderDropdown(employees) { + if (employees.length === 0) { + dropdown.innerHTML = '
No employees found
'; + } else { + dropdown.innerHTML = employees.map(function (emp) { + const label = (emp.lastName === '(no record)') + ? 'ID: ' + emp.id + ' (no record)' + : emp.lastName + ', ' + emp.firstName; + const safeLabel = (emp.lastName === '(no record)') + ? 'ID: ' + emp.id + : emp.lastName + ', ' + emp.firstName; + return '
' + + '' + label + '' + + 'ID: ' + emp.id + '' + + '
'; + }).join(''); + } + positionDropdown(); + dropdown.classList.add('show'); + } + + function positionDropdown() { + const rect = searchInput.getBoundingClientRect(); + dropdown.style.top = (rect.bottom + window.scrollY) + 'px'; + dropdown.style.left = rect.left + 'px'; + dropdown.style.width = rect.width + 'px'; + } + + window.taSelectEmployee = function (id, displayName) { + hiddenInput.value = id; + searchInput.value = displayName; + dropdown.classList.remove('show'); + }; + + window.taClearEmployee = function () { + searchInput.value = ''; + hiddenInput.value = ''; + searchInput.closest('form').submit(); + }; + + window.addEventListener('scroll', function () { + if (dropdown.classList.contains('show')) positionDropdown(); + }, true); + + window.addEventListener('resize', function () { + if (dropdown.classList.contains('show')) positionDropdown(); + }); +}());