Files
project-managment/templates/users/list.html
T
sankalp b7d43b57f4 feat(templates/users): adopt role objects for branding and selection
Replace role name comparisons with slug checks and display role.name to support dynamic role definitions.
2026-06-29 06:56:55 +05:45

445 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}Users{% endblock %}
{% block breadcrumb %}
<nav class="mb-4" aria-label="Breadcrumb">
<ol class="flex items-center gap-1.5 text-xs text-gray-500 dark:text-gray-400">
<li><a href="{% url 'dashboard' %}" class="hover:text-gray-700 dark:hover:text-gray-300">Dashboard</a></li>
<li class="text-gray-300 dark:text-gray-600">/</li>
<li class="text-gray-900 dark:text-white font-medium" aria-current="page">Users</li>
</ol>
</nav>
{% endblock %}
{% block content %}
<div x-data="{ columns: {{ columns|safe }}, openCols: false, selectedIds: [], selectAll: false, viewModal: false, viewUser: null, editModal: false, editUser: {}, userModalMode: 'edit' }">
<div class="mb-6 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 class="text-2xl font-bold">Users</h1>
<p class="text-gray-500 dark:text-gray-400 text-sm mt-1">{{ page_obj.paginator.count }} active users</p>
</div>
<div class="flex items-center gap-2">
<button type="button" @click="editUser = { username: '', email: '', first_name: '', last_name: '', role: '', is_active: true }; userModalMode = 'create'; editModal = true" class="dk-btn dk-btn-primary text-sm"><i class="fas fa-plus"></i> New User</button>
<form class="flex gap-2" method="get">
<input type="text" name="search" value="{{ search }}" placeholder="Search by name or email..."
class="text-sm border border-gray-200 dark:border-gray-600 rounded-lg px-3 py-2 bg-white dark:bg-gray-950 focus:outline-none focus:ring-2 focus:ring-blue-500 w-64">
<button type="submit" class="dk-btn dk-btn-primary text-sm"><i class="fas fa-search"></i> Search</button>
{% if search %}
<a href="{% url 'users-list' %}" class="dk-btn dk-btn-ghost text-sm"><i class="fas fa-times"></i> Clear</a>
{% endif %}
</form>
</div>
</div>
<form id="users-bulk-delete-form" method="post" action="{% url 'users-bulk-delete' %}">
{% csrf_token %}
<template x-for="id in selectedIds" x-key="id">
<input type="hidden" name="ids" :value="id">
</template>
</form>
<div x-show="selectedIds.length > 0" x-cloak class="mb-4 flex items-center gap-3 px-4 py-2.5 bg-primary-50 dark:bg-primary-900/20 border border-primary-200 dark:border-primary-800 rounded-lg">
<span class="text-sm font-medium text-primary-700 dark:text-primary-300">
<span x-text="selectedIds.length"></span> selected
</span>
<button type="button" @click="confirmAction({title:'Delete Users', message:'Delete ' + selectedIds.length + ' user(s)?', confirmText:'Delete', danger:true, onConfirm:function(){ document.getElementById('users-bulk-delete-form').submit() }})" class="dk-btn dk-btn-danger text-xs px-3 py-1">
<i class="fas fa-trash mr-1"></i> Delete Selected
</button>
<button type="button" @click="selectedIds = []; selectAll = false" class="text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 ml-auto">
<i class="fas fa-times mr-1"></i> Clear selection
</button>
</div>
<div class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10">
<div class="px-6 py-3 border-b border-gray-100 dark:border-gray-600 flex flex-wrap items-center justify-between gap-3">
<form id="user-filters" method="get" class="flex flex-wrap items-center gap-3">
<label class="flex items-center gap-1.5 text-xs text-gray-600 dark:text-gray-400 cursor-pointer">
<input type="checkbox" name="has_projects" value="1" {% if has_projects %}checked{% endif %} onchange="this.form.submit()" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
Has projects
</label>
<label class="flex items-center gap-1.5 text-xs text-gray-600 dark:text-gray-400 cursor-pointer">
<input type="checkbox" name="has_assigned" value="1" {% if has_assigned %}checked{% endif %} onchange="this.form.submit()" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
Has assigned
</label>
<label class="flex items-center gap-1.5 text-xs text-gray-600 dark:text-gray-400 cursor-pointer">
<input type="checkbox" name="has_created" value="1" {% if has_created %}checked{% endif %} onchange="this.form.submit()" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
Has created
</label>
<select name="role" onchange="this.form.submit()" class="text-xs border border-gray-200 dark:border-gray-600 rounded-lg px-2 py-1.5 bg-white dark:bg-gray-950 text-gray-700 dark:text-gray-300">
<option value="">All roles</option>
{% for r in roles %}
<option value="{{ r.slug }}" {% if role == r.slug %}selected{% endif %}>{{ r.name }}</option>
{% endfor %}
</select>
{% if has_projects or has_assigned or has_created or role %}
<a href="{% url 'users-list' %}" class="text-xs text-blue-600 dark:text-blue-400 hover:text-blue-700 font-medium">Clear filters</a>
{% endif %}
</form>
<div class="flex items-center gap-3">
<div class="relative">
<button @click="openCols = !openCols" class="text-xs flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300">
<i class="fas fa-columns text-[10px]"></i> Columns
</button>
<div x-show="openCols" @click.outside="openCols = false" x-cloak class="absolute right-0 mt-1 w-44 bg-white dark:bg-gray-950 rounded-lg shadow-lg ring-1 ring-gray-950/5 dark:ring-white/10 z-50 py-1">
<form method="get" id="columns-form">
{% for c in all_columns %}
<label class="flex items-center gap-2 px-3 py-1.5 text-xs text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer">
<input type="checkbox" name="columns" value="{{ c }}" {% if c in columns %}checked{% endif %} onchange="document.getElementById('columns-form').submit()" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
{{ c|title }}
</label>
{% endfor %}
{% for key, val in request.GET.items %}{% if key != 'columns' and key != 'page' %}<input type="hidden" name="{{ key }}" value="{{ val }}">{% endif %}{% endfor %}
</form>
</div>
</div>
<select name="per_page" onchange="this.form.submit()" form="user-filters" class="text-xs border border-gray-200 dark:border-gray-600 rounded-lg px-2 py-1.5 bg-white dark:bg-gray-950 text-gray-700 dark:text-gray-300">
<option value="5" {% if per_page == 5 %}selected{% endif %}>5 per page</option>
<option value="10" {% if per_page == 10 %}selected{% endif %}>10 per page</option>
<option value="25" {% if per_page == 25 %}selected{% endif %}>25 per page</option>
<option value="50" {% if per_page == 50 %}selected{% endif %}>50 per page</option>
</select>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-gray-100 dark:border-gray-600 bg-gray-50 dark:bg-gray-800">
<th class="px-3 py-3 w-10">
<input type="checkbox" @change="selectAll = !selectAll; selectedIds = selectAll ? Array.from(document.querySelectorAll('.user-checkbox')).map(cb => cb.value) : []" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
</th>
{% if "name" in columns %}<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Name</th>{% endif %}
{% if "email" in columns %}<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Email</th>{% endif %}
{% if "roles" in columns %}<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Roles</th>{% endif %}
{% if "projects" in columns %}<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Projects</th>{% endif %}
{% if "assigned" in columns %}<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Assigned</th>{% endif %}
{% if "created" in columns %}<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Created</th>{% endif %}
{% if "joined" in columns %}<th class="px-6 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Joined</th>{% endif %}
<th class="px-6 py-3 text-right text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody>
{% for u in users %}
<tr class="border-b border-gray-100 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800/30 transition-colors" :class="selectedIds.includes('{{ u.id }}') && 'bg-blue-50 dark:bg-blue-900/10'">
<td class="px-3 py-3">
<input type="checkbox" class="user-checkbox rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500" value="{{ u.id }}" x-model="selectedIds">
</td>
{% if "name" in columns %}
<td class="px-6 py-3">
<div class="flex items-center gap-3">
{% if u.profile_image %}
<img src="{{ u.profile_image.url }}" alt="{{ u.get_full_name|default:u.username }}" class="w-9 h-9 rounded-full object-cover shrink-0">
{% else %}
<div class="w-9 h-9 rounded-full flex items-center justify-center text-white font-bold text-sm shrink-0" style="background-color:{% cycle '#3B82F6' '#10B981' '#8B5CF6' '#F59E0B' '#EF4444' '#06B6D4' '#F97316' '#EC4899' %}">
{{ u.get_full_name|default:u.username|make_list|first|upper }}
</div>
{% endif %}
<div>
<p class="font-medium text-sm text-gray-900 dark:text-white">{{ u.get_full_name|default:u.username }}</p>
<p class="text-xs text-gray-400">@{{ u.username }}</p>
</div>
</div>
</td>
{% endif %}
{% if "email" in columns %}
<td class="px-6 py-3 text-sm text-gray-600 dark:text-gray-400">{{ u.email|default:"-" }}</td>
{% endif %}
{% if "roles" in columns %}
<td class="px-6 py-3">
{% if u.role %}
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium
{% if u.role.slug in admin_roles %}bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400
{% else %}bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400{% endif %}">
{{ u.role.name }}
</span>
{% else %}
-
{% endif %}
</td>
{% endif %}
{% if "projects" in columns %}
<td class="px-6 py-3 text-center text-sm font-medium text-gray-900 dark:text-white">{{ u.project_count }}</td>
{% endif %}
{% if "assigned" in columns %}
<td class="px-6 py-3 text-center text-sm font-medium text-gray-900 dark:text-white">{{ u.assigned_count }}</td>
{% endif %}
{% if "created" in columns %}
<td class="px-6 py-3 text-center text-sm font-medium text-gray-900 dark:text-white">{{ u.ticket_count }}</td>
{% endif %}
{% if "joined" in columns %}
<td class="px-6 py-3 text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ u.date_joined|date:"M d, Y" }}</td>
{% endif %}
<td class="px-6 py-3 text-right">
<div class="flex items-center justify-end gap-1">
<button type="button"
@click="viewUser = usersData['{{ u.id }}']; viewModal = true"
class="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors shadow-sm">View</button>
<button type="button"
@click="editUser = Object.assign({}, usersData['{{ u.id }}']); userModalMode = 'edit'; editModal = true"
class="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors shadow-sm">Edit</button>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="9" class="text-center py-12 text-gray-400">
<i class="fas fa-users text-4xl block mb-3 text-gray-300 dark:text-gray-600"></i>
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">No users found</p>
{% if search %}
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">No users match your search criteria.</p>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if page_obj.has_other_pages %}
<div class="px-6 py-3 border-t border-gray-100 dark:border-gray-600 flex items-center justify-between">
<p class="text-xs text-gray-500 dark:text-gray-400">
Showing {{ page_obj.start_index }}–{{ page_obj.end_index }} of {{ page_obj.paginator.count }}
</p>
<div class="flex items-center gap-1">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}{% if search %}&search={{ search }}{% endif %}{% if has_projects %}&has_projects=1{% endif %}{% if has_assigned %}&has_assigned=1{% endif %}{% if has_created %}&has_created=1{% endif %}{% if role %}&role={{ role }}{% endif %}{% if per_page != 10 %}&per_page={{ per_page }}{% endif %}" class="px-2.5 py-1 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800">Previous</a>
{% endif %}
{% for n in page_obj.paginator.page_range %}
{% if n == page_obj.number %}
<span class="px-2.5 py-1 rounded text-xs font-medium bg-blue-600 text-white">{{ n }}</span>
{% elif n >= page_obj.number|add:"-2" and n <= page_obj.number|add:"2" %}
<a href="?page={{ n }}{% if search %}&search={{ search }}{% endif %}{% if has_projects %}&has_projects=1{% endif %}{% if has_assigned %}&has_assigned=1{% endif %}{% if has_created %}&has_created=1{% endif %}{% if role %}&role={{ role }}{% endif %}{% if per_page != 10 %}&per_page={{ per_page }}{% endif %}" class="px-2.5 py-1 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800">{{ n }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}{% if search %}&search={{ search }}{% endif %}{% if has_projects %}&has_projects=1{% endif %}{% if has_assigned %}&has_assigned=1{% endif %}{% if has_created %}&has_created=1{% endif %}{% if role %}&role={{ role }}{% endif %}{% if per_page != 10 %}&per_page={{ per_page }}{% endif %}" class="px-2.5 py-1 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800">Next</a>
{% endif %}
</div>
</div>
{% endif %}
</div>
<!-- View User Modal -->
<div x-show="viewModal" x-cloak class="fixed inset-0 z-50 flex items-center justify-center"
@keydown.escape.window="viewModal = false">
<div class="fixed inset-0 bg-black/50" @click="viewModal = false"></div>
<div class="relative bg-white dark:bg-gray-900 rounded-xl shadow-xl border border-gray-200 dark:border-gray-700 w-full max-w-lg mx-4 sm:mx-auto my-auto z-10 max-h-[90vh] overflow-y-auto">
<!-- Header -->
<div class="px-6 py-5 flex items-center justify-between">
<h3 class="text-base font-bold text-gray-900 dark:text-white">View user</h3>
<button type="button" @click="viewModal = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<!-- Body -->
<div class="px-6 pb-4 space-y-4" x-show="viewUser">
<!-- Row 1: Name | Email -->
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Name</label>
<div class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
x-text="viewUser?.full_name || '—'"></div>
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Email</label>
<div class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
x-text="viewUser?.email || '—'"></div>
</div>
</div>
<!-- Row 2: Joined | Last Login -->
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Joined</label>
<div class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
x-text="viewUser?.date_joined || '—'"></div>
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Last Login</label>
<div class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400"
x-text="viewUser?.last_login || 'Never'"></div>
</div>
</div>
<!-- Row 3: Roles -->
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Roles</label>
<div class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 flex items-center gap-2">
<span class="inline-flex items-center px-2.5 py-0.5 rounded text-xs font-medium"
:class="{
'bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300': viewUser?.role === 'superadmin',
'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300': viewUser?.role === 'manager',
'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300': true
}"
x-text="viewUser?.role_label || viewUser?.role || '—'">
</span>
</div>
</div>
</div>
<!-- Footer -->
<div class="px-6 pb-6 pt-2 flex flex-wrap gap-2.5">
<button type="button" @click="viewModal = false"
class="px-4 py-1.5 text-sm font-medium text-gray-900 dark:text-gray-200 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
Close
</button>
</div>
</div>
</div>
<!-- Edit/Create User Modal -->
<div x-show="editModal" x-cloak class="fixed inset-0 z-50 flex items-center justify-center"
@keydown.escape.window="editModal = false">
<div class="fixed inset-0 bg-black/50" @click="editModal = false"></div>
<div class="relative bg-white dark:bg-gray-900 rounded-xl shadow-xl border border-gray-200 dark:border-gray-700 w-full max-w-3xl mx-4 sm:mx-auto my-auto z-10 max-h-[90vh] overflow-y-auto">
<!-- Header -->
<div class="px-6 py-5 flex items-center justify-between border-b border-gray-100 dark:border-gray-800">
<h3 class="text-base font-bold text-gray-900 dark:text-white" x-text="userModalMode === 'create' ? 'Create User' : 'Edit ' + (editUser?.full_name || 'User')">Edit User</h3>
<button type="button" @click="editModal = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<!-- Form -->
<form method="post" :action="userModalMode === 'create' ? '/users/create/' : '/users/' + editUser?.id + '/edit/'" enctype="multipart/form-data">
{% csrf_token %}
<div class="px-6 py-4 space-y-4" x-show="editUser">
<!-- Row 1: Username | Email -->
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Username <span class="text-red-500">*</span></label>
<input type="text" name="username" x-model="editUser.username" required
class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Email <span class="text-red-500">*</span></label>
<input type="email" name="email" x-model="editUser.email" required
class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
</div>
<!-- Row 2: First Name | Last Name -->
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">First Name</label>
<input type="text" name="first_name" x-model="editUser.first_name"
class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Last Name</label>
<input type="text" name="last_name" x-model="editUser.last_name"
class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
</div>
<!-- Password Fields (Create Mode Only) -->
<template x-if="userModalMode === 'create'">
<div class="grid grid-cols-2 gap-4" x-data="{ showPass1: false, showPass2: false }">
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Password <span class="text-red-500">*</span></label>
<div class="relative">
<input :type="showPass1 ? 'text' : 'password'" name="password1" required placeholder="Password"
class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 pr-10">
<button type="button" @click="showPass1 = !showPass1" class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<i class="fas" :class="showPass1 ? 'fa-eye-slash' : 'fa-eye'"></i>
</button>
</div>
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Confirm Password <span class="text-red-500">*</span></label>
<div class="relative">
<input :type="showPass2 ? 'text' : 'password'" name="password2" required placeholder="Confirm password"
class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 pr-10">
<button type="button" @click="showPass2 = !showPass2" class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<i class="fas" :class="showPass2 ? 'fa-eye-slash' : 'fa-eye'"></i>
</button>
</div>
</div>
</div>
</template>
<!-- Row 3: Role -->
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Role</label>
<select name="role" x-model="editUser.role"
class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
{% for role in roles %}
<option value="{{ role.slug }}">{{ role.name }}</option>
{% endfor %}
</select>
</div>
<!-- Row 4: Profile Image -->
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Profile Image</label>
<input type="file" name="profile_image"
class="w-full text-xs text-gray-500 border border-gray-200 dark:border-gray-700 rounded-lg px-2 py-1.5 bg-white dark:bg-gray-800 focus:outline-none file:mr-3 file:py-1 file:px-2 file:rounded file:border-0 file:text-xs file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100">
</div>
<!-- Row 5: Active Toggle -->
<div class="mt-4">
<input type="hidden" name="is_active" :value="editUser.is_active ? '1' : '0'">
<label class="flex items-start gap-3 cursor-pointer" @click="editUser.is_active = !editUser.is_active">
<div role="switch" class="mt-0.5 relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:style="editUser.is_active ? 'background-color: var(--color-primary, #3b82f6)' : 'background-color: #d1d5db'">
<span class="pointer-events-none relative inline-block h-4 w-4 rounded-full bg-white shadow-sm ring-0 transition-transform duration-200 ease-in-out"
:style="editUser.is_active ? 'transform: translateX(16px)' : 'transform: translateX(0)'"></span>
</div>
<div>
<p class="text-sm font-semibold text-gray-900 dark:text-white">Active</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">User is active and can log in</p>
</div>
</label>
</div>
</div>
<!-- Footer -->
<div class="px-6 py-5 border-t border-gray-100 dark:border-gray-800 flex justify-end gap-2">
<button type="button" @click="editModal = false"
class="px-4 py-1.5 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
Cancel
</button>
<button type="submit"
class="px-4 py-1.5 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors shadow-sm"
x-text="userModalMode === 'create' ? 'Create User' : 'Save Changes'">
Save Changes
</button>
</div>
</form>
</div>
</div>
<script>
const usersData = {
{% for u in users %}
"{{ u.id }}": {
id: {{ u.id }},
full_name: "{{ u.get_full_name|default:u.username|escapejs }}",
username: "{{ u.username|escapejs }}",
email: "{{ u.email|escapejs }}",
first_name: "{{ u.first_name|escapejs }}",
last_name: "{{ u.last_name|escapejs }}",
role: "{{ u.role.slug|default:'' }}",
role_label: "{{ u.role.name|default:'' }}",
is_active: {{ u.is_active|yesno:'true,false' }},
date_joined: "{{ u.date_joined|date:'M d, Y' }}",
last_login: "{{ u.last_login|date:'M d, Y'|default:'Never' }}",
project_count: {{ u.project_count }},
assigned_count: {{ u.assigned_count }},
ticket_count: {{ u.ticket_count }},
initials: "{{ u.get_full_name|default:u.username|make_list|first|upper|escapejs }}",
avatar_color: "{% cycle '#3B82F6' '#10B981' '#8B5CF6' '#F59E0B' '#EF4444' '#06B6D4' '#F97316' '#EC4899' %}"
}{% if not forloop.last %},{% endif %}
{% endfor %}
};
</script>
{% endblock %}