feat: Implement supervisor trainee management features, including search, add, and remove functionalities
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
{% if results %}
|
||||
<div class="card border-secondary bg-light">
|
||||
<div class="card-body p-2">
|
||||
<small class="text-muted d-block mb-2">{{ results|length }} result{{ results|pluralize }}</small>
|
||||
<ul class="list-group list-group-sm">
|
||||
{% for user in results %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center py-2">
|
||||
<div>
|
||||
<div class="small fw-semibold">{{ user.get_full_name|default:user.username }}</div>
|
||||
<div class="text-muted" style="font-size: 0.85rem;">{{ user.email|default:"no email" }}</div>
|
||||
</div>
|
||||
<form method="post" action="{% url 'generic:supervisor_trainee_add' supervisor.pk user.pk %}" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<button type="submit"
|
||||
class="btn btn-sm btn-outline-success"
|
||||
hx-post="{% url 'generic:supervisor_trainee_add' supervisor.pk user.pk %}"
|
||||
hx-target="#trainees-list-content"
|
||||
hx-swap="outerHTML">
|
||||
Add
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,55 @@
|
||||
{% load django_htmx %}
|
||||
{% if supervisor.trainee.all %}
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for trainee in supervisor.trainee.all %}
|
||||
{% with trainee_user=trainee.user %}
|
||||
<li class="list-group-item px-0 py-3 trainee-row">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
|
||||
<div>
|
||||
<div class="fw-semibold">
|
||||
{% if trainee_user %}
|
||||
{{ trainee_user.get_full_name|default:trainee_user.username }}
|
||||
{% else %}
|
||||
{{ trainee }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
{% if trainee_user and trainee_user.email %}
|
||||
{{ trainee_user.email }}
|
||||
{% else %}
|
||||
No email on file
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-1">
|
||||
{% if trainee_user %}
|
||||
<a href="{% url 'account_profile' trainee_user.username %}"
|
||||
class="btn btn-sm btn-outline-secondary">
|
||||
Profile
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if request.user == supervisor.user or request.user.is_superuser or request.user.groups.filter|length %}
|
||||
<form method="post" action="{% url 'generic:supervisor_trainee_remove' supervisor.pk trainee_user.pk %}" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<button type="submit"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
hx-post="{% url 'generic:supervisor_trainee_remove' supervisor.pk trainee_user.pk %}"
|
||||
hx-target="#trainees-list-content"
|
||||
hx-swap="outerHTML"
|
||||
hx-confirm="Remove {{ trainee_user.get_full_name|default:trainee_user.username }}?">
|
||||
Remove
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="alert alert-secondary mb-0">
|
||||
No trainees are currently linked to this supervisor.
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -1,29 +1,103 @@
|
||||
{% extends 'generic/supervisor_base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Supervisor: <span id="name">{{object.name}}</span></h2>
|
||||
<span id="email">{{object.email}}</span>
|
||||
|
||||
<br/>User account: <span id="user">{{object.user}}</span>
|
||||
<br/>Site: <span id="site">{{object.site}}</span>
|
||||
<br/>Trainee(s): {% for trainee in object.trainee.all %}
|
||||
<a href="{% url 'account_profile' trainee.user.username %}">{{trainee.user}}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
<div><a href="{% url 'generic:supervisor_overview' pk=object.pk %}">Supervisor overview</a></div>
|
||||
|
||||
|
||||
<div>
|
||||
<a href="{% url 'generic:supervisor_edit' pk=object.pk %}"> Edit</a>
|
||||
<a href="{% url 'generic:supervisor_delete' pk=object.pk %}"> Delete</a>
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-4">
|
||||
<div>
|
||||
<h2 class="mb-1">{{ object.name }}</h2>
|
||||
<p class="text-muted mb-0">
|
||||
Supervisor profile and trainee links.
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-md-end">
|
||||
<span class="badge bg-info-subtle text-info-emphasis rounded-pill px-3 py-2">
|
||||
{{ object.trainee.count }} trainee{{ object.trainee.count|pluralize }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12 col-lg-7">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-3">Supervisor Details</h5>
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-sm-4 text-muted">Name</dt>
|
||||
<dd class="col-sm-8" id="name">{{ object.name|default:"-" }}</dd>
|
||||
|
||||
<dt class="col-sm-4 text-muted">Email</dt>
|
||||
<dd class="col-sm-8" id="email">
|
||||
{% if object.email %}
|
||||
<a href="mailto:{{ object.email }}" class="link-body-emphasis">{{ object.email }}</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-4 text-muted">User Account</dt>
|
||||
<dd class="col-sm-8" id="user">{{ object.user|default:"-" }}</dd>
|
||||
|
||||
<dt class="col-sm-4 text-muted">Site</dt>
|
||||
<dd class="col-sm-8" id="site">{{ object.site|default:"-" }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-lg-5">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-3">Actions</h5>
|
||||
<div class="d-grid gap-2">
|
||||
<a class="btn btn-outline-primary" href="{% url 'generic:supervisor_overview' pk=object.pk %}">
|
||||
Supervisor Overview
|
||||
</a>
|
||||
{% if request.user.is_superuser or request.user.groups.filter|length %}
|
||||
<a class="btn btn-outline-secondary" href="{% url 'generic:supervisor_edit' pk=object.pk %}">
|
||||
Edit Supervisor
|
||||
</a>
|
||||
<a class="btn btn-outline-danger" href="{% url 'generic:supervisor_delete' pk=object.pk %}">
|
||||
Delete Supervisor
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm mb-3">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h5 class="card-title mb-0">Trainees</h5>
|
||||
<span class="text-muted small">{{ object.trainee.count }} linked</span>
|
||||
</div>
|
||||
|
||||
{% if request.user == object.user or request.user.is_superuser or request.user.groups.filter|length %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label small text-muted">Search to add trainee</label>
|
||||
<input type="search"
|
||||
class="form-control form-control-sm"
|
||||
placeholder="Name, email, username…"
|
||||
hx-get="{% url 'generic:supervisor_trainee_search' object.pk %}"
|
||||
hx-target="#trainee-search-results"
|
||||
hx-trigger="input delay:300ms"
|
||||
name="q">
|
||||
</div>
|
||||
<div id="trainee-search-results" class="mb-3"></div>
|
||||
{% endif %}
|
||||
|
||||
<div id="trainees-list-content">
|
||||
{% include 'generic/partials/supervisor_trainees_list.html' with supervisor=object %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
|
||||
{% block css %}
|
||||
{{ block.super }}
|
||||
<style>
|
||||
td, th { padding-left: 10px }
|
||||
.trainee-row:not(:last-child) {
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user