diff --git a/app.py b/app.py index e28dfac..e6d618c 100644 --- a/app.py +++ b/app.py @@ -4618,20 +4618,30 @@ def attendance_report(): date_from = request.args.get('date_from', '') date_to = request.args.get('date_to', '') location_filter = request.args.get('location', '') + # employee param is now a comma-separated list of IDs (multi-employee filter) employee_filter = request.args.get('employee', '') project_filter = request.args.get('project', '') - # Get employee display name for filter if employee ID is provided - employee_display_name = '' - if employee_filter: + # Build the list of selected employee IDs (strip blanks) + employee_ids = [e.strip() for e in employee_filter.split(',') if e.strip()] if employee_filter else [] + + # Build display names for each selected employee + employee_display_names = [] + for eid in employee_ids: try: - employee = Employee.query.filter_by(id=int(employee_filter)).first() - if employee: - employee_display_name = f"{employee.lastName}, {employee.firstName}" + emp = Employee.query.filter_by(id=int(eid)).first() + if emp: + employee_display_names.append({ + 'id': eid, + 'name': f"{emp.lastName}, {emp.firstName}" + }) else: - employee_display_name = f"ID: {employee_filter}" + employee_display_names.append({'id': eid, 'name': f"ID: {eid}"}) except (ValueError, TypeError): - employee_display_name = employee_filter + employee_display_names.append({'id': eid, 'name': eid}) + + # Legacy single-value display name (kept for backward compat in template) + employee_display_name = ', '.join([e['name'] for e in employee_display_names]) # ============================================================ # PROJECT MANAGER ACCESS CONTROL @@ -4692,6 +4702,8 @@ def attendance_report(): date_to=date_to, location_filter=location_filter, employee_filter=employee_filter, + employee_ids=employee_ids, + employee_display_names=employee_display_names, employee_display_name=employee_display_name, project_filter=project_filter, today_date=datetime.now().strftime('%Y-%m-%d'), @@ -4799,9 +4811,19 @@ def attendance_report(): filter_conditions.append("ad.location_name = :location") query_params['location'] = location_filter - if employee_filter: - filter_conditions.append("ad.employee_id = :employee") - query_params['employee'] = employee_filter + if employee_ids: + if len(employee_ids) == 1: + filter_conditions.append("ad.employee_id = :employee_0") + query_params['employee_0'] = employee_ids[0] + else: + placeholders = ', '.join([f':employee_{i}' for i in range(len(employee_ids))]) + filter_conditions.append(f"ad.employee_id IN ({placeholders})") + for i, eid in enumerate(employee_ids): + query_params[f'employee_{i}'] = eid + logger_handler.logger.info( + f"Attendance report filtered by employee IDs: {employee_ids} " + f"by user {session.get('username', 'unknown')}" + ) if project_filter: filter_conditions.append("qc.project_id = :project") @@ -5052,6 +5074,8 @@ def attendance_report(): date_to=date_to, location_filter=location_filter, employee_filter=employee_filter, + employee_ids=employee_ids, + employee_display_names=employee_display_names, employee_display_name=employee_display_name, project_filter=project_filter, today_date=datetime.now().strftime('%Y-%m-%d'), diff --git a/static/css/attendance.css b/static/css/attendance.css index 9fe7bd8..e48078b 100644 --- a/static/css/attendance.css +++ b/static/css/attendance.css @@ -1647,4 +1647,103 @@ tr.modified-record { .filter-input { padding-right: 35px !important; -} \ No newline at end of file +} + +/* ─── Multi-Employee Chip Filter ─────────────────────────────── */ +.employee-chips-wrapper { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px; + min-height: 38px; + padding: 4px 36px 4px 8px; + border: 1px solid #d1d5db; + border-radius: 6px; + background: #fff; + cursor: text; + position: relative; +} + +.employee-chips-wrapper:focus-within { + border-color: #6366f1; + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15); +} + +.employee-chip { + display: inline-flex; + align-items: center; + gap: 5px; + background: #eef2ff; + color: #4338ca; + border: 1px solid #c7d2fe; + border-radius: 4px; + padding: 2px 6px 2px 8px; + font-size: 0.825rem; + font-weight: 500; + white-space: nowrap; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; +} + +.employee-chip-remove { + background: none; + border: none; + cursor: pointer; + color: #6366f1; + padding: 0; + line-height: 1; + font-size: 0.75rem; + display: flex; + align-items: center; + border-radius: 2px; + transition: color 0.15s; + flex-shrink: 0; +} + +.employee-chip-remove:hover { + color: #dc2626; +} + +.employee-chip-input { + border: none; + outline: none; + font-size: 0.875rem; + background: transparent; + flex: 1; + min-width: 120px; + padding: 2px 0; + color: #1f2937; +} + +.employee-chip-input::placeholder { + color: #9ca3af; +} + +.employee-chips-clear-all { + position: absolute; + right: 6px; + top: 50%; + transform: translateY(-50%); + background: #dc3545; + color: white; + border: none; + border-radius: 50%; + width: 22px; + height: 22px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.2s; + padding: 0; +} + +.employee-chips-clear-all:hover { + background: #c82333; +} + +.employee-chips-clear-all i { + font-size: 0.7rem; +} +/* ─── End Multi-Employee Chip Filter ─────────────────────────── */ \ No newline at end of file diff --git a/templates/attendance_report.html b/templates/attendance_report.html index 90eca03..b93e2bd 100644 --- a/templates/attendance_report.html +++ b/templates/attendance_report.html @@ -127,30 +127,33 @@
-
@@ -755,73 +758,170 @@ document.addEventListener('DOMContentLoaded', function() { {% endblock %} \ No newline at end of file