Enhance trainees table with sortable columns and improved bulk edit options
This commit is contained in:
@@ -19,22 +19,25 @@
|
|||||||
{% endif %}Trainees
|
{% endif %}Trainees
|
||||||
</h2>
|
</h2>
|
||||||
<form>
|
<form>
|
||||||
<table>
|
<table id="trainees-table">
|
||||||
<tr class="header">
|
<tr class="header">
|
||||||
<th>Name</th>
|
<th class="sortable" data-col="0">Name <span class="sort-indicator"></span></th>
|
||||||
<th>Grade</th><th>Email</th><th>Supervisor</th><th>Edit</th>
|
<th class="sortable" data-col="1">Grade <span class="sort-indicator"></span></th>
|
||||||
|
<th class="sortable" data-col="2">Email <span class="sort-indicator"></span></th>
|
||||||
|
<th class="sortable" data-col="3">Supervisor <span class="sort-indicator"></span></th>
|
||||||
|
<th>Edit</th>
|
||||||
<th class="trainee-bulk-edit">Bulk Edit<br/>
|
<th class="trainee-bulk-edit">Bulk Edit<br/>
|
||||||
<span _="on click log 'test' then log 'hello' then repeat for i in <input/>
|
<span _="on click log 'test' then log 'hello' then repeat for i in <input/>
|
||||||
-- set i.checked to not i.checked
|
-- set i.checked to not i.checked
|
||||||
toggle [@checked] on i
|
toggle [@checked] on i
|
||||||
">Toggle All</span>
|
">Toggle All</span>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for trainee in trainees %}
|
{% for trainee in trainees %}
|
||||||
<tr class="trainee" _="on click log 'test' then log the next <input/> then toggle [@checked] on <input/> in me">
|
<tr class="trainee" _="on click log 'test' then log the next <input/> then toggle [@checked] on <input/> in me">
|
||||||
<td>{{trainee.user.first_name}} {{trainee.user.last_name}}
|
<td>{{trainee.user.first_name}} {{trainee.user.last_name}}
|
||||||
<a href="{% url 'account_profile' trainee.username %}" class="opacity-25"><i class="bi bi-link"></i></a>
|
<a href="{% url 'account_profile' trainee.username %}" class="opacity-25"><i class="bi bi-link"></i></a>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
{% if not trainee.grade %}
|
{% if not trainee.grade %}
|
||||||
@@ -50,11 +53,11 @@
|
|||||||
class="no-supervisor"
|
class="no-supervisor"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
>{{trainee.supervisor}}
|
>{{trainee.supervisor}}
|
||||||
{% if not trainee.supervisor %}
|
{% if not trainee.supervisor %}
|
||||||
<a href="{% url 'account_profile_update' trainee.user.username %}?redirect={{request.path}}" class="add-supervisor">add</a>
|
<a href="{% url 'account_profile_update' trainee.user.username %}?redirect={{request.path}}" class="add-supervisor">add</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'generic:supervisor_detail' trainee.supervisor.pk %}" class="opacity-25"><i class="bi bi-link"></i></a>
|
<a href="{% url 'generic:supervisor_detail' trainee.supervisor.pk %}" class="opacity-25"><i class="bi bi-link"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td><a href="{% url 'account_update' trainee.user.username %}?redirect={{request.path}}">User</a>/<a href="{% url 'account_profile_update' trainee.user.username %}?redirect={{request.path}}">Profile</a>
|
<td><a href="{% url 'account_update' trainee.user.username %}?redirect={{request.path}}">User</a>/<a href="{% url 'account_profile_update' trainee.user.username %}?redirect={{request.path}}">Profile</a>
|
||||||
<span class="hover-highlight remove-trainee"><i class="bi bi-x-circle" title="Click to remove trainee status"
|
<span class="hover-highlight remove-trainee"><i class="bi bi-x-circle" title="Click to remove trainee status"
|
||||||
@@ -80,35 +83,35 @@
|
|||||||
_="on click toggle .show on .trainee-bulk-edit"
|
_="on click toggle .show on .trainee-bulk-edit"
|
||||||
>Bulk edit trainees</button>
|
>Bulk edit trainees</button>
|
||||||
|
|
||||||
<div id="bulk-edit" class="trainee-bulk-edit">
|
<div id="bulk-edit" class="trainee-bulk-edit">
|
||||||
<h3>Bulk edit options</h3>
|
<h3>Bulk edit options</h3>
|
||||||
These actions will act on all the selected trainees. Refresh the page to cancel / clear.
|
These actions will act on all the selected trainees. Refresh the page to cancel / clear.
|
||||||
<div id="htmx-info"></div>
|
<div id="htmx-info"></div>
|
||||||
<div id="htmx-options"></div>
|
<div id="htmx-options"></div>
|
||||||
<button id="bulk-delete-supervisors-button"
|
<button id="bulk-delete-supervisors-button"
|
||||||
title="Deletes the supervisors of all selected users"
|
title="Deletes the supervisors of all selected users"
|
||||||
hx-post="{% url 'generic:users_bulk_delete_supervisors' %}"
|
hx-post="{% url 'generic:users_bulk_delete_supervisors' %}"
|
||||||
hx-include="[name='selection']"
|
hx-include="[name='selection']"
|
||||||
hx-confirm="This will delete supervisors from all selected users, are you sure you wish to continue?"
|
hx-confirm="This will delete supervisors from all selected users, are you sure you wish to continue?"
|
||||||
hx-target="#htmx-info"
|
hx-target="#htmx-info"
|
||||||
_='on click put "" into #htmx-options'
|
_='on click put "" into #htmx-options'
|
||||||
>Delete supervisors</button>
|
>Delete supervisors</button>
|
||||||
<button id="bulk-edit-grade-button"
|
<button id="bulk-edit-grade-button"
|
||||||
hx-post="{% url 'generic:users_bulk_edit' %}"
|
hx-post="{% url 'generic:users_bulk_edit' %}"
|
||||||
hx-include="[name='selection']"
|
hx-include="[name='selection']"
|
||||||
hx-target="#htmx-options"
|
hx-target="#htmx-options"
|
||||||
title="Changes the selected users grade."
|
title="Changes the selected users grade."
|
||||||
|
|
||||||
>Edit grade</button>
|
>Edit grade</button>
|
||||||
<button id="bulk-deactivate-user-button"
|
<button id="bulk-deactivate-user-button"
|
||||||
hx-post="{% url 'generic:users_bulk_edit' %}"
|
hx-post="{% url 'generic:users_bulk_edit' %}"
|
||||||
hx-include="[name='selection']"
|
hx-include="[name='selection']"
|
||||||
hx-target="#htmx-options"
|
hx-target="#htmx-options"
|
||||||
hx-confirm="This will deactivate all selected users, are you sure you wish to continue?"
|
hx-confirm="This will deactivate all selected users, are you sure you wish to continue?"
|
||||||
title="Deactivates the selected users"
|
title="Deactivates the selected users"
|
||||||
|
|
||||||
>Deactivate</button>
|
>Deactivate</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.add-supervisor, .add-grade {
|
.add-supervisor, .add-grade {
|
||||||
@@ -144,6 +147,63 @@
|
|||||||
background-color: darkgrey;
|
background-color: darkgrey;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
th.sortable {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
th .sort-indicator {
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin-left: 6px;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
const table = document.getElementById('trainees-table');
|
||||||
|
if (!table) return;
|
||||||
|
|
||||||
|
const getCellValue = (tr, idx) => {
|
||||||
|
const cell = tr.children[idx];
|
||||||
|
return cell ? cell.innerText.trim().toLowerCase() : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const comparer = function(idx, asc){
|
||||||
|
return function(a,b){
|
||||||
|
const v1 = getCellValue(a, idx);
|
||||||
|
const v2 = getCellValue(b, idx);
|
||||||
|
// attempt numeric compare
|
||||||
|
const n1 = parseFloat(v1.replace(/[^0-9.\-]/g, ''));
|
||||||
|
const n2 = parseFloat(v2.replace(/[^0-9.\-]/g, ''));
|
||||||
|
if (!isNaN(n1) && !isNaN(n2)){
|
||||||
|
return (n1 - n2) * (asc ? 1 : -1);
|
||||||
|
}
|
||||||
|
return v1.localeCompare(v2) * (asc ? 1 : -1);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let lastSorted = {idx: null, asc: true};
|
||||||
|
|
||||||
|
table.querySelectorAll('th.sortable').forEach(th => {
|
||||||
|
th.addEventListener('click', function(){
|
||||||
|
const idx = parseInt(th.getAttribute('data-col'));
|
||||||
|
const tbodyRows = Array.from(table.querySelectorAll('tr.trainee'));
|
||||||
|
const asc = (lastSorted.idx === idx) ? !lastSorted.asc : true;
|
||||||
|
|
||||||
|
tbodyRows.sort(comparer(idx, asc));
|
||||||
|
|
||||||
|
// re-append rows in new order
|
||||||
|
const parent = table;
|
||||||
|
tbodyRows.forEach(r => parent.appendChild(r));
|
||||||
|
|
||||||
|
// update indicators
|
||||||
|
table.querySelectorAll('th.sortable .sort-indicator').forEach(el => el.textContent = '');
|
||||||
|
th.querySelector('.sort-indicator').textContent = asc ? '▲' : '▼';
|
||||||
|
|
||||||
|
lastSorted = {idx, asc};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user