Updated activate/deactivate user function
This commit is contained in:
@@ -1506,18 +1506,141 @@ def edit_user(user_id):
|
|||||||
flash('Error updating user. Please try again.', 'error')
|
flash('Error updating user. Please try again.', 'error')
|
||||||
return redirect(url_for('users'))
|
return redirect(url_for('users'))
|
||||||
|
|
||||||
|
@app.route('/users/<int:user_id>/toggle_status', methods=['POST'])
|
||||||
|
@admin_required
|
||||||
|
def toggle_user_status(user_id):
|
||||||
|
"""Toggle user active status (Admin only)"""
|
||||||
|
try:
|
||||||
|
user_to_toggle = User.query.get(user_id)
|
||||||
|
current_user = User.query.get(session['user_id'])
|
||||||
|
|
||||||
|
if not user_to_toggle:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': 'User not found.'
|
||||||
|
}), 404
|
||||||
|
|
||||||
|
# Prevent self-deactivation
|
||||||
|
if user_to_toggle.id == current_user.id:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': 'You cannot deactivate yourself.'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# Check if this is the last admin being deactivated
|
||||||
|
if (user_to_toggle.role == 'admin' and user_to_toggle.active_status == True):
|
||||||
|
active_admin_count = User.query.filter_by(role='admin', active_status=True).count()
|
||||||
|
if active_admin_count <= 1:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': 'Cannot deactivate the last admin user.'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# Toggle the status
|
||||||
|
new_status = not user_to_toggle.active_status
|
||||||
|
user_to_toggle.active_status = new_status
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
action = 'activated' if new_status else 'deactivated'
|
||||||
|
message = f'"{user_to_toggle.full_name}" has been {action} successfully.'
|
||||||
|
|
||||||
|
print(f"Admin {current_user.username} {action} user {user_to_toggle.username}")
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': message,
|
||||||
|
'new_status': new_status,
|
||||||
|
'user_id': user_id
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
|
print(f"Error toggling user status: {e}")
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': 'Error updating user status. Please try again.'
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
@app.route('/users/<int:user_id>/activate', methods=['GET', 'POST'])
|
||||||
|
@admin_required
|
||||||
|
def activate_user(user_id):
|
||||||
|
"""Activate a user (Admin only) - Alternative route"""
|
||||||
|
try:
|
||||||
|
user_to_activate = User.query.get(user_id)
|
||||||
|
current_user = User.query.get(session['user_id'])
|
||||||
|
|
||||||
|
if not user_to_activate:
|
||||||
|
flash('User not found.', 'error')
|
||||||
|
return redirect(url_for('users'))
|
||||||
|
|
||||||
|
if user_to_activate.active_status:
|
||||||
|
flash('User is already active.', 'info')
|
||||||
|
else:
|
||||||
|
user_to_activate.active_status = True
|
||||||
|
db.session.commit()
|
||||||
|
flash(f'"{user_to_activate.full_name}" has been activated.', 'success')
|
||||||
|
print(f"Admin {current_user.username} activated user {user_to_activate.username}")
|
||||||
|
|
||||||
|
return redirect(url_for('users'))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
|
print(f"Error activating user: {e}")
|
||||||
|
flash('Error activating user. Please try again.', 'error')
|
||||||
|
return redirect(url_for('users'))
|
||||||
|
|
||||||
|
@app.route('/users/<int:user_id>/deactivate', methods=['GET', 'POST'])
|
||||||
|
@admin_required
|
||||||
|
def deactivate_user(user_id):
|
||||||
|
"""Deactivate a user (Admin only) - Alternative route"""
|
||||||
|
try:
|
||||||
|
user_to_deactivate = User.query.get(user_id)
|
||||||
|
current_user = User.query.get(session['user_id'])
|
||||||
|
|
||||||
|
if not user_to_deactivate:
|
||||||
|
flash('User not found.', 'error')
|
||||||
|
return redirect(url_for('users'))
|
||||||
|
|
||||||
|
# Prevent self-deactivation
|
||||||
|
if user_to_deactivate.id == current_user.id:
|
||||||
|
flash('You cannot deactivate yourself.', 'error')
|
||||||
|
return redirect(url_for('users'))
|
||||||
|
|
||||||
|
# Check if this is the last admin
|
||||||
|
if user_to_deactivate.role == 'admin' and user_to_deactivate.active_status:
|
||||||
|
active_admin_count = User.query.filter_by(role='admin', active_status=True).count()
|
||||||
|
if active_admin_count <= 1:
|
||||||
|
flash('Cannot deactivate the last admin user.', 'error')
|
||||||
|
return redirect(url_for('users'))
|
||||||
|
|
||||||
|
if not user_to_deactivate.active_status:
|
||||||
|
flash('User is already deactivated.', 'info')
|
||||||
|
else:
|
||||||
|
user_to_deactivate.active_status = False
|
||||||
|
db.session.commit()
|
||||||
|
flash(f'"{user_to_deactivate.full_name}" has been deactivated.', 'success')
|
||||||
|
print(f"Admin {current_user.username} deactivated user {user_to_deactivate.username}")
|
||||||
|
|
||||||
|
return redirect(url_for('users'))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
|
print(f"Error deactivating user: {e}")
|
||||||
|
flash('Error deactivating user. Please try again.', 'error')
|
||||||
|
return redirect(url_for('users'))
|
||||||
|
|
||||||
# ENHANCED USER STATISTICS API
|
# ENHANCED USER STATISTICS API
|
||||||
@app.route('/api/users/stats')
|
@app.route('/api/users/stats')
|
||||||
@admin_required
|
@admin_required
|
||||||
def user_stats_api():
|
def user_stats_api():
|
||||||
"""API endpoint for user statistics"""
|
"""UPDATED: API endpoint for user statistics - includes new roles"""
|
||||||
payroll_users = User.query.filter_by(role='payroll', active_status=True).count()
|
|
||||||
project_manager_users = User.query.filter_by(role='project_manager', active_status=True).count()
|
|
||||||
try:
|
try:
|
||||||
total_users = User.query.count()
|
total_users = User.query.count()
|
||||||
active_users = User.query.filter_by(active_status=True).count()
|
active_users = User.query.filter_by(active_status=True).count()
|
||||||
admin_users = User.query.filter_by(role='admin', active_status=True).count()
|
admin_users = User.query.filter_by(role='admin', active_status=True).count()
|
||||||
staff_users = User.query.filter_by(role='staff', active_status=True).count()
|
staff_users = User.query.filter_by(role='staff', active_status=True).count()
|
||||||
|
payroll_users = User.query.filter_by(role='payroll', active_status=True).count()
|
||||||
|
project_manager_users = User.query.filter_by(role='project_manager', active_status=True).count()
|
||||||
inactive_users = User.query.filter_by(active_status=False).count()
|
inactive_users = User.query.filter_by(active_status=False).count()
|
||||||
|
|
||||||
# Recent registrations (last 30 days)
|
# Recent registrations (last 30 days)
|
||||||
|
|||||||
+230
-149
@@ -1,11 +1,12 @@
|
|||||||
{% extends "base_authenticated.html" %} {% block title %}User Management - QR
|
{% extends "base_authenticated.html" %}
|
||||||
Code Management{% endblock %} {% block extra_head %}
|
{% block title %}User Management - QR Code Management{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_head %}
|
||||||
<!-- Dedicated Users Page CSS -->
|
<!-- Dedicated Users Page CSS -->
|
||||||
<link
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/users.css') }}">
|
||||||
rel="stylesheet"
|
{% endblock %}
|
||||||
href="{{ url_for('static', filename='css/users.css') }}"
|
|
||||||
/>
|
{% block content %}
|
||||||
{% endblock %} {% block content %}
|
|
||||||
<div class="users-page">
|
<div class="users-page">
|
||||||
<div class="users-header">
|
<div class="users-header">
|
||||||
<div class="header-content">
|
<div class="header-content">
|
||||||
@@ -31,10 +32,7 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
<i class="fas fa-crown"></i>
|
<i class="fas fa-crown"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<h3>
|
<h3>{{ users|selectattr('role', 'equalto', 'admin')|selectattr('active_status', 'equalto', True)|list|length }}</h3>
|
||||||
{{ users|selectattr('role', 'equalto',
|
|
||||||
'admin')|selectattr('active_status', 'equalto', True)|list|length }}
|
|
||||||
</h3>
|
|
||||||
<p>Active Administrators</p>
|
<p>Active Administrators</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,10 +42,7 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
<i class="fas fa-users"></i>
|
<i class="fas fa-users"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<h3>
|
<h3>{{ users|selectattr('role', 'equalto', 'staff')|selectattr('active_status', 'equalto', True)|list|length }}</h3>
|
||||||
{{ users|selectattr('role', 'equalto',
|
|
||||||
'staff')|selectattr('active_status', 'equalto', True)|list|length }}
|
|
||||||
</h3>
|
|
||||||
<p>Active Staff Members</p>
|
<p>Active Staff Members</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,10 +52,7 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
<i class="fas fa-calculator"></i>
|
<i class="fas fa-calculator"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<h3>
|
<h3>{{ users|selectattr('role', 'equalto', 'payroll')|selectattr('active_status', 'equalto', True)|list|length }}</h3>
|
||||||
{{ users|selectattr('role', 'equalto',
|
|
||||||
'payroll')|selectattr('active_status', 'equalto', True)|list|length }}
|
|
||||||
</h3>
|
|
||||||
<p>Payroll Specialists</p>
|
<p>Payroll Specialists</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,11 +62,7 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
<i class="fas fa-project-diagram"></i>
|
<i class="fas fa-project-diagram"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<h3>
|
<h3>{{ users|selectattr('role', 'equalto', 'project_manager')|selectattr('active_status', 'equalto', True)|list|length }}</h3>
|
||||||
{{ users|selectattr('role', 'equalto',
|
|
||||||
'project_manager')|selectattr('active_status', 'equalto',
|
|
||||||
True)|list|length }}
|
|
||||||
</h3>
|
|
||||||
<p>Project Managers</p>
|
<p>Project Managers</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,9 +72,7 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
<i class="fas fa-user-check"></i>
|
<i class="fas fa-user-check"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<h3>
|
<h3>{{ users|selectattr('active_status', 'equalto', True)|list|length }}</h3>
|
||||||
{{ users|selectattr('active_status', 'equalto', True)|list|length }}
|
|
||||||
</h3>
|
|
||||||
<p>Total Active Users</p>
|
<p>Total Active Users</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,9 +82,7 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
<i class="fas fa-user-times"></i>
|
<i class="fas fa-user-times"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<h3>
|
<h3>{{ users|selectattr('active_status', 'equalto', False)|list|length }}</h3>
|
||||||
{{ users|selectattr('active_status', 'equalto', False)|list|length }}
|
|
||||||
</h3>
|
|
||||||
<p>Inactive Users</p>
|
<p>Inactive Users</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -168,7 +152,7 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
data-status="{{ 'active' if user.active_status else 'inactive' }}"
|
data-status="{{ 'active' if user.active_status else 'inactive' }}"
|
||||||
>
|
>
|
||||||
<td>{{ loop.index }}</td>
|
<td>{{ loop.index }}</td>
|
||||||
|
|
||||||
<!-- User Information -->
|
<!-- User Information -->
|
||||||
<td>
|
<td>
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
@@ -194,27 +178,28 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
<td>
|
<td>
|
||||||
<div class="user-role {{ user.role }}">
|
<div class="user-role {{ user.role }}">
|
||||||
{% if user.role == 'admin' %}
|
{% if user.role == 'admin' %}
|
||||||
<i class="fas fa-crown"></i>
|
<i class="fas fa-crown"></i>
|
||||||
Administrator {% elif user.role == 'staff' %}
|
Administrator
|
||||||
<i class="fas fa-user"></i>
|
{% elif user.role == 'staff' %}
|
||||||
Staff User {% elif user.role == 'payroll' %}
|
<i class="fas fa-user"></i>
|
||||||
<i class="fas fa-calculator"></i>
|
Staff User
|
||||||
Payroll Specialist {% elif user.role == 'project_manager' %}
|
{% elif user.role == 'payroll' %}
|
||||||
<i class="fas fa-project-diagram"></i>
|
<i class="fas fa-calculator"></i>
|
||||||
Project Manager {% else %}
|
Payroll Specialist
|
||||||
<i class="fas fa-user"></i>
|
{% elif user.role == 'project_manager' %}
|
||||||
{{ user.role.title() }} {% endif %}
|
<i class="fas fa-project-diagram"></i>
|
||||||
|
Project Manager
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-user"></i>
|
||||||
|
{{ user.role.title() }}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<td>
|
<td>
|
||||||
<div
|
<div class="user-status {{ 'active' if user.active_status else 'inactive' }}">
|
||||||
class="user-status {{ 'active' if user.active_status else 'inactive' }}"
|
<i class="fas fa-{{ 'check-circle' if user.active_status else 'times-circle' }}"></i>
|
||||||
>
|
|
||||||
<i
|
|
||||||
class="fas fa-{{ 'check-circle' if user.active_status else 'times-circle' }}"
|
|
||||||
></i>
|
|
||||||
{{ 'Active' if user.active_status else 'Inactive' }}
|
{{ 'Active' if user.active_status else 'Inactive' }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -222,10 +207,7 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
<!-- QR Codes Count -->
|
<!-- QR Codes Count -->
|
||||||
<td class="user-qr-count">
|
<td class="user-qr-count">
|
||||||
<div class="qr-stats">
|
<div class="qr-stats">
|
||||||
<span class="qr-count"
|
<span class="qr-count">{{ user.created_qr_codes.filter_by(active_status=True).count() }}</span>
|
||||||
>{{ user.created_qr_codes.filter_by(active_status=True).count()
|
|
||||||
}}</span
|
|
||||||
>
|
|
||||||
<small>QR Codes</small>
|
<small>QR Codes</small>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -233,32 +215,24 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
<!-- Created Date -->
|
<!-- Created Date -->
|
||||||
<td>
|
<td>
|
||||||
{% if user.created_date %}
|
{% if user.created_date %}
|
||||||
<div class="date-info">
|
<div class="date-info">
|
||||||
<div class="date-main">
|
<div class="date-main">{{ user.created_date.strftime('%m/%d/%Y') }}</div>
|
||||||
{{ user.created_date.strftime('%m/%d/%Y') }}
|
<div class="date-time">{{ user.created_date.strftime('%I:%M %p') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="date-time">
|
|
||||||
{{ user.created_date.strftime('%I:%M %p') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="never-logged-in">Unknown</span>
|
<span class="never-logged-in">Unknown</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Last Login -->
|
<!-- Last Login -->
|
||||||
<td>
|
<td>
|
||||||
{% if user.last_login_date %}
|
{% if user.last_login_date %}
|
||||||
<div class="date-info">
|
<div class="date-info">
|
||||||
<div class="date-main">
|
<div class="date-main">{{ user.last_login_date.strftime('%m/%d/%Y') }}</div>
|
||||||
{{ user.last_login_date.strftime('%m/%d/%Y') }}
|
<div class="date-time">{{ user.last_login_date.strftime('%I:%M %p') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="date-time">
|
|
||||||
{{ user.last_login_date.strftime('%I:%M %p') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="never-logged-in">Never</span>
|
<span class="never-logged-in">Never</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
@@ -266,64 +240,50 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
<td class="user-actions">
|
<td class="user-actions">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<!-- Edit Button -->
|
<!-- Edit Button -->
|
||||||
<a
|
<a href="{{ url_for('edit_user', user_id=user.id) }}"
|
||||||
href="{{ url_for('edit_user', user_id=user.id) }}"
|
class="btn btn-sm btn-primary"
|
||||||
class="btn btn-sm btn-primary"
|
title="Edit User">
|
||||||
title="Edit User"
|
|
||||||
>
|
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% if user.role != 'admin' %}
|
{% if user.role != 'admin' %}
|
||||||
<!-- Promote to Admin -->
|
<!-- Promote to Admin -->
|
||||||
<a
|
<a href="{{ url_for('promote_user', user_id=user.id) }}"
|
||||||
href="{{ url_for('promote_user', user_id=user.id) }}"
|
class="btn btn-sm btn-success"
|
||||||
class="btn btn-sm btn-success"
|
title="Promote to Admin"
|
||||||
title="Promote to Admin"
|
onclick="return confirm('Are you sure you want to promote {{ user.full_name }} to admin?')">
|
||||||
onclick="return confirm('Are you sure you want to promote {{ user.full_name }} to admin?')"
|
<i class="fas fa-arrow-up"></i>
|
||||||
>
|
</a>
|
||||||
<i class="fas fa-arrow-up"></i>
|
|
||||||
</a>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<!-- Demote from Admin -->
|
<!-- Demote from Admin -->
|
||||||
{% if users|selectattr('role', 'equalto',
|
{% if users|selectattr('role', 'equalto', 'admin')|selectattr('active_status', 'equalto', True)|list|length > 1 %}
|
||||||
'admin')|selectattr('active_status', 'equalto', True)|list|length
|
<a href="{{ url_for('demote_user', user_id=user.id) }}"
|
||||||
> 1 %}
|
class="btn btn-sm btn-warning"
|
||||||
<a
|
title="Demote from Admin"
|
||||||
href="{{ url_for('demote_user', user_id=user.id) }}"
|
onclick="return confirm('Are you sure you want to demote {{ user.full_name }} from admin?')">
|
||||||
class="btn btn-sm btn-warning"
|
<i class="fas fa-arrow-down"></i>
|
||||||
title="Demote from Admin"
|
</a>
|
||||||
onclick="return confirm('Are you sure you want to demote {{ user.full_name }} from admin?')"
|
{% else %}
|
||||||
>
|
<button class="btn btn-sm btn-secondary"
|
||||||
<i class="fas fa-arrow-down"></i>
|
title="Cannot demote last admin" disabled>
|
||||||
</a>
|
<i class="fas fa-arrow-down"></i>
|
||||||
{% else %}
|
</button>
|
||||||
<button
|
{% endif %}
|
||||||
class="btn btn-sm btn-secondary"
|
{% endif %}
|
||||||
title="Cannot demote last admin"
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
<i class="fas fa-arrow-down"></i>
|
|
||||||
</button>
|
|
||||||
{% endif %} {% endif %}
|
|
||||||
|
|
||||||
<!-- Status Toggle -->
|
<!-- Status Toggle -->
|
||||||
{% if user.active_status %}
|
{% if user.active_status %}
|
||||||
<button
|
<button class="btn btn-sm btn-danger"
|
||||||
class="btn btn-sm btn-danger"
|
title="Deactivate User"
|
||||||
title="Deactivate User"
|
onclick="toggleUserStatus({{ user.id }}, false)">
|
||||||
onclick="toggleUserStatus({{ user.id }}, false)"
|
<i class="fas fa-user-times"></i>
|
||||||
>
|
</button>
|
||||||
<i class="fas fa-user-times"></i>
|
|
||||||
</button>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<button
|
<button class="btn btn-sm btn-success"
|
||||||
class="btn btn-sm btn-success"
|
title="Activate User"
|
||||||
title="Activate User"
|
onclick="toggleUserStatus({{ user.id }}, true)">
|
||||||
onclick="toggleUserStatus({{ user.id }}, true)"
|
<i class="fas fa-user-check"></i>
|
||||||
>
|
</button>
|
||||||
<i class="fas fa-user-check"></i>
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -348,13 +308,15 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block extra_scripts %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_scripts %}
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const searchInput = document.getElementById("searchUsers");
|
const searchInput = document.getElementById('searchUsers');
|
||||||
const roleFilter = document.getElementById("roleFilter");
|
const roleFilter = document.getElementById('roleFilter');
|
||||||
const statusFilter = document.getElementById("statusFilter");
|
const statusFilter = document.getElementById('statusFilter');
|
||||||
const userRows = document.querySelectorAll(".user-row");
|
const userRows = document.querySelectorAll('.user-row');
|
||||||
|
|
||||||
// Search and filter functionality
|
// Search and filter functionality
|
||||||
function filterUsers() {
|
function filterUsers() {
|
||||||
@@ -362,26 +324,25 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
const selectedRole = roleFilter.value;
|
const selectedRole = roleFilter.value;
|
||||||
const selectedStatus = statusFilter.value;
|
const selectedStatus = statusFilter.value;
|
||||||
|
|
||||||
userRows.forEach((row) => {
|
userRows.forEach(row => {
|
||||||
const name = row.dataset.name || "";
|
const name = row.dataset.name || '';
|
||||||
const email = row.dataset.email || "";
|
const email = row.dataset.email || '';
|
||||||
const username = row.dataset.username || "";
|
const username = row.dataset.username || '';
|
||||||
const role = row.dataset.role || "";
|
const role = row.dataset.role || '';
|
||||||
const status = row.dataset.status || "";
|
const status = row.dataset.status || '';
|
||||||
|
|
||||||
const matchesSearch =
|
const matchesSearch = !searchTerm ||
|
||||||
!searchTerm ||
|
name.includes(searchTerm) ||
|
||||||
name.includes(searchTerm) ||
|
email.includes(searchTerm) ||
|
||||||
email.includes(searchTerm) ||
|
|
||||||
username.includes(searchTerm);
|
username.includes(searchTerm);
|
||||||
|
|
||||||
const matchesRole = !selectedRole || role === selectedRole;
|
const matchesRole = !selectedRole || role === selectedRole;
|
||||||
const matchesStatus = !selectedStatus || status === selectedStatus;
|
const matchesStatus = !selectedStatus || status === selectedStatus;
|
||||||
|
|
||||||
if (matchesSearch && matchesRole && matchesStatus) {
|
if (matchesSearch && matchesRole && matchesStatus) {
|
||||||
row.style.display = "";
|
row.style.display = '';
|
||||||
} else {
|
} else {
|
||||||
row.style.display = "none";
|
row.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -389,11 +350,9 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateRowNumbers() {
|
function updateRowNumbers() {
|
||||||
const visibleRows = Array.from(userRows).filter(
|
const visibleRows = Array.from(userRows).filter(row => row.style.display !== 'none');
|
||||||
(row) => row.style.display !== "none"
|
|
||||||
);
|
|
||||||
visibleRows.forEach((row, index) => {
|
visibleRows.forEach((row, index) => {
|
||||||
const numberCell = row.querySelector("td:first-child");
|
const numberCell = row.querySelector('td:first-child');
|
||||||
if (numberCell) {
|
if (numberCell) {
|
||||||
numberCell.textContent = index + 1;
|
numberCell.textContent = index + 1;
|
||||||
}
|
}
|
||||||
@@ -401,9 +360,9 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Event listeners
|
// Event listeners
|
||||||
searchInput.addEventListener("input", filterUsers);
|
searchInput.addEventListener('input', filterUsers);
|
||||||
roleFilter.addEventListener("change", filterUsers);
|
roleFilter.addEventListener('change', filterUsers);
|
||||||
statusFilter.addEventListener("change", filterUsers);
|
statusFilter.addEventListener('change', filterUsers);
|
||||||
|
|
||||||
// Initial filter on page load
|
// Initial filter on page load
|
||||||
filterUsers();
|
filterUsers();
|
||||||
@@ -414,16 +373,138 @@ Code Management{% endblock %} {% block extra_head %}
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggleUserStatus(userId, newStatus) {
|
function toggleUserStatus(userId, newStatus) {
|
||||||
const action = newStatus ? "activate" : "deactivate";
|
const action = newStatus ? 'activate' : 'deactivate';
|
||||||
const message = `Are you sure you want to ${action} this user?`;
|
const message = `Are you sure you want to ${action} this user?`;
|
||||||
|
|
||||||
if (confirm(message)) {
|
if (confirm(message)) {
|
||||||
// Here you would implement the actual status toggle
|
// Show loading state
|
||||||
// For now, just reload the page
|
const button = event.target.closest('button');
|
||||||
// In a full implementation, you'd make an AJAX call to update the status
|
const originalHTML = button.innerHTML;
|
||||||
alert(`User ${action}d successfully!`);
|
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
||||||
window.location.reload();
|
button.disabled = true;
|
||||||
|
|
||||||
|
// Make AJAX call to toggle status
|
||||||
|
fetch(`/users/${userId}/toggle_status`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
new_status: newStatus
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// Update the UI immediately
|
||||||
|
const userRow = button.closest('.user-row');
|
||||||
|
const statusCell = userRow.querySelector('.user-status');
|
||||||
|
const statusToggleCell = userRow.querySelector('.user-actions');
|
||||||
|
|
||||||
|
// Update status badge
|
||||||
|
if (data.new_status) {
|
||||||
|
statusCell.innerHTML = '<div class="user-status active"><i class="fas fa-check-circle"></i> Active</div>';
|
||||||
|
} else {
|
||||||
|
statusCell.innerHTML = '<div class="user-status inactive"><i class="fas fa-times-circle"></i> Inactive</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the button
|
||||||
|
if (data.new_status) {
|
||||||
|
button.className = 'btn btn-sm btn-danger';
|
||||||
|
button.title = 'Deactivate User';
|
||||||
|
button.setAttribute('onclick', `toggleUserStatus(${userId}, false)`);
|
||||||
|
button.innerHTML = '<i class="fas fa-user-times"></i>';
|
||||||
|
} else {
|
||||||
|
button.className = 'btn btn-sm btn-success';
|
||||||
|
button.title = 'Activate User';
|
||||||
|
button.setAttribute('onclick', `toggleUserStatus(${userId}, true)`);
|
||||||
|
button.innerHTML = '<i class="fas fa-user-check"></i>';
|
||||||
|
}
|
||||||
|
|
||||||
|
button.disabled = false;
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
showNotification(data.message, 'success');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Show error message
|
||||||
|
showNotification(data.message, 'error');
|
||||||
|
button.innerHTML = originalHTML;
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
showNotification('Network error. Please try again.', 'error');
|
||||||
|
button.innerHTML = originalHTML;
|
||||||
|
button.disabled = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showNotification(message, type) {
|
||||||
|
// Create notification element
|
||||||
|
const notification = document.createElement('div');
|
||||||
|
notification.className = `alert alert-${type} notification`;
|
||||||
|
notification.innerHTML = `
|
||||||
|
<i class="fas ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'}"></i>
|
||||||
|
${message}
|
||||||
|
<button class="alert-close" onclick="this.parentElement.remove()">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add notification to page
|
||||||
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
|
// Style the notification
|
||||||
|
Object.assign(notification.style, {
|
||||||
|
position: 'fixed',
|
||||||
|
top: '20px',
|
||||||
|
right: '20px',
|
||||||
|
zIndex: '9999',
|
||||||
|
minWidth: '300px',
|
||||||
|
padding: '12px 16px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
||||||
|
backgroundColor: type === 'success' ? '#10b981' : '#ef4444',
|
||||||
|
color: 'white',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
animation: 'slideInRight 0.3s ease-out'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto remove after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
if (notification.parentElement) {
|
||||||
|
notification.style.animation = 'slideOutRight 0.3s ease-out';
|
||||||
|
setTimeout(() => notification.remove(), 300);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add CSS animations for notifications
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from { transform: translateX(100%); opacity: 0; }
|
||||||
|
to { transform: translateX(0); opacity: 1; }
|
||||||
|
}
|
||||||
|
@keyframes slideOutRight {
|
||||||
|
from { transform: translateX(0); opacity: 1; }
|
||||||
|
to { transform: translateX(100%); opacity: 0; }
|
||||||
|
}
|
||||||
|
.alert-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user