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:
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user