Enhance user sorting and email resend functionality in CID group view: add sorting options for user list and improve email resend handling in candidate details view

This commit is contained in:
Ross
2025-12-15 12:15:42 +00:00
parent 77edc17732
commit 885ab1aad4
2 changed files with 106 additions and 23 deletions
@@ -29,9 +29,21 @@
<div class="card-body">
<h3 class="card-title mb-2">Users</h3>
{% if users %}
<ul class="list-group list-group-flush">
<div class="d-flex align-items-center mb-2">
<div class="me-auto small text-muted">Sort by:</div>
<div class="btn-group btn-group-sm user-sort-group" role="group" aria-label="Sort">
<button type="button" class="btn btn-sm btn-outline-secondary sort-btn" data-key="username">{% if group_type == 'cid' %}CID{% else %}Username{% endif %} <span class="sort-caret"></span></button>
<button type="button" class="btn btn-sm btn-outline-secondary sort-btn" data-key="name">Name <span class="sort-caret"></span></button>
<button type="button" class="btn btn-sm btn-outline-secondary sort-btn" data-key="email">Email <span class="sort-caret"></span></button>
</div>
</div>
<ul id="group-users-list" class="list-group list-group-flush">
{% for user in users %}
<li class="list-group-item bg-transparent border-0 py-2">
<li class="list-group-item bg-transparent border-0 py-2"
data-username="{% if group_type == 'cid' %}{{ user.cid|default:''|escape }}{% else %}{{ user.username|default:''|escape }}{% endif %}"
data-name="{% if group_type == 'cid' %}{{ user.name|default:''|escape }}{% else %}{{ user.get_full_name|default:user.username|escape }}{% endif %}"
data-email="{{ user.email|default:''|escape }}">
{% if group_type == "cid" %}
<div class="d-flex align-items-start">
<div>
@@ -41,6 +53,18 @@
<div class="ms-auto text-end small text-muted">
<div>CID: {{ user.cid }}</div>
<div>Passcode: {{ user.passcode }}</div>
<div class="mt-1">
{% if user.login_email_sent %}
<span class="badge bg-success ms-1 login-email-status">Email sent</span>
{% else %}
<span class="badge bg-secondary ms-1 login-email-status">Not sent</span>
{% endif %}
{% if user.email %}
<button type="button" class="btn btn-sm btn-outline-primary ms-2 send-login-email-btn" data-cid="{{ user.cid }}" data-url="{% url 'generic:candidate_email_details' user.cid %}">Send</button>
{% else %}
<span class="small text-muted ms-2">No email</span>
{% endif %}
</div>
</div>
</div>
{% else %}
@@ -54,29 +78,15 @@
</ul>
{% else %}
<p class="small text-muted">This group has no users.
{% if group_type == "cid" %}
You can create or add users <a href="{% url 'generic:manage_cids' %}">here</a>.
{% endif %}
{% if group_type == "cid" %}
You can create or add users <a href="{% url 'generic:manage_cids' %}">here</a>.
{% endif %}
</p>
{% endif %}
</div>
</div>
<div class="card mb-3">
<div class="card-body">
<h4 class="card-title mb-2">Add users by email</h4>
<form method="post" class="mb-2">
{% csrf_token %}
<div class="mb-2">
<textarea name="emails" placeholder="Email addresses (one per line)" class="form-control form-control-sm" rows="4"></textarea>
</div>
<button type="submit" class="btn btn-sm btn-primary"
hx-post="{% url 'generic:user_group_add_by_email_user' group.pk %}"
hx-target="#add-users-by-email-results">Add</button>
</form>
<div id="add-users-by-email-results" class="small text-muted"></div>
</div>
</div>
<div class="card mb-3">
<div class="card-body">
@@ -153,5 +163,68 @@
.list-group-item .fw-bold { font-size: 0.95rem; }
.card-title { font-size: 1rem; }
/* keep existing small visual tweaks */
.sort-caret { font-weight: 600; }
</style>
<script>
document.addEventListener('DOMContentLoaded', function(){
(function(){
const sortGroup = document.querySelector('.user-sort-group');
if(!sortGroup) return;
let sortState = { key: 'username', dir: 'asc' };
function updateCarets(){
sortGroup.querySelectorAll('.sort-btn').forEach(function(b){
const key = b.dataset.key;
const caret = b.querySelector('.sort-caret');
if(!caret) return;
caret.textContent = (sortState.key === key) ? (sortState.dir === 'asc' ? ' ▲' : ' ▼') : '';
});
}
function getVal(li, key){
if(!li) return '';
return (li.dataset[key] || '').toString().toLowerCase();
}
function sortList(key){
const ul = document.getElementById('group-users-list'); if(!ul) return;
const items = Array.from(ul.children);
const dir = (sortState.key === key && sortState.dir === 'asc') ? 'desc' : 'asc';
items.sort(function(a,b){ const va = getVal(a,key); const vb = getVal(b,key); if(va < vb) return dir === 'asc' ? -1 : 1; if(va > vb) return dir === 'asc' ? 1 : -1; return 0; });
items.forEach(function(it){ ul.appendChild(it); });
sortState = { key: key, dir: dir };
updateCarets();
}
sortGroup.querySelectorAll('.sort-btn').forEach(function(b){ b.addEventListener('click', function(){ sortList(b.dataset.key); }); });
updateCarets();
})();
});
</script>
<script>
// Send/resend login email handlers for CID users
document.addEventListener('DOMContentLoaded', function(){
document.body.addEventListener('click', function(e){
const btn = e.target.closest && e.target.closest('.send-login-email-btn');
if(!btn) return;
e.preventDefault();
const url = btn.dataset.url + '?resend=1';
const cid = btn.dataset.cid;
// show spinner on button
btn.disabled = true;
const spinner = document.createElement('span'); spinner.className = 'spinner-border spinner-border-sm ms-2'; spinner.setAttribute('role','status'); spinner.setAttribute('aria-hidden','true'); btn.appendChild(spinner);
fetch(url, { method: 'GET', credentials: 'same-origin' })
.then(r => r.json())
.then(data => {
const li = btn.closest('li');
if(!li) return;
const status = li.querySelector('.login-email-status');
if(data.sent){
if(status){ status.textContent = 'Email sent'; status.className = 'badge bg-success ms-1 login-email-status'; }
} else {
if(status){ status.textContent = 'Not sent'; status.className = 'badge bg-secondary ms-1 login-email-status'; }
if(data.comment){ console.error('Email send comment:', data.comment); }
}
}).catch(err => { console.error('Failed to send login email', err); })
.finally(()=>{ if(btn){ const s = btn.querySelector('.spinner-border'); if(s) s.remove(); btn.disabled = false; } });
});
});
</script>
{% endblock %}
+13 -3
View File
@@ -4959,11 +4959,21 @@ def group_email_results(request, pk, resend=False):
@user_is_cid_user_manager
def candidate_email_details(request, cid, resend=False):
user = CidUser.objects.get(cid=cid)
user = get_object_or_404(CidUser, cid=cid)
email_sent, comment = user.email_details(resend=False)
# Allow caller to request a resend via querystring or POST param
resend_flag = False
try:
if request.method == 'POST':
resend_flag = str(request.POST.get('resend', '')).lower() in ('1', 'true', 'yes')
else:
resend_flag = str(request.GET.get('resend', '')).lower() in ('1', 'true', 'yes')
except Exception:
resend_flag = False
return JsonResponse({"sent": email_sent, "comment": comment})
email_sent, comment = user.email_details(resend=resend_flag)
return JsonResponse({"sent": email_sent, "comment": str(comment)})
@user_is_cid_user_manager