381 lines
17 KiB
HTML
381 lines
17 KiB
HTML
{% extends "layouts/base.html" %}
|
|
|
|
{% load i18n %}
|
|
|
|
{% block title %}{% trans "Staff Management" %} - PX360{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h1 class="page-title">{% trans "Staff Management" %}</h1>
|
|
<p class="text-muted">{% trans "Manage hospital staff and their user accounts" %}</p>
|
|
</div>
|
|
{% if user.is_px_admin or user.is_hospital_admin %}
|
|
<a href="{% url 'organizations:staff_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus"></i> {% trans "Add New Staff" %}
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="card mb-4">
|
|
<div class="card-body">
|
|
<form method="get" class="row g-3">
|
|
<div class="col-md-3">
|
|
<label class="form-label">{% trans "Hospital" %}</label>
|
|
<select name="hospital" class="form-select">
|
|
<option value="">{% trans "All Hospitals" %}</option>
|
|
{% for hospital in hospitals %}
|
|
<option value="{{ hospital.id }}" {% if request.GET.hospital == hospital.id|stringformat:"s" %}selected{% endif %}>
|
|
{{ hospital.name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label">{% trans "Status" %}</label>
|
|
<select name="status" class="form-select">
|
|
<option value="">{% trans "All Status" %}</option>
|
|
<option value="active" {% if request.GET.status == 'active' %}selected{% endif %}>{% trans "Active" %}</option>
|
|
<option value="inactive" {% if request.GET.status == 'inactive' %}selected{% endif %}>{% trans "Inactive" %}</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label">{% trans "Staff Type" %}</label>
|
|
<select name="staff_type" class="form-select">
|
|
<option value="">{% trans "All Types" %}</option>
|
|
<option value="physician" {% if request.GET.staff_type == 'physician' %}selected{% endif %}>{% trans "Physician" %}</option>
|
|
<option value="nurse" {% if request.GET.staff_type == 'nurse' %}selected{% endif %}>{% trans "Nurse" %}</option>
|
|
<option value="admin" {% if request.GET.staff_type == 'admin' %}selected{% endif %}>{% trans "Administrative" %}</option>
|
|
<option value="other" {% if request.GET.staff_type == 'other' %}selected{% endif %}>{% trans "Other" %}</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">{% trans "Search" %}</label>
|
|
<input type="text" name="search" class="form-control" placeholder="{% trans 'Name, ID, or License...' %}" value="{{ request.GET.search|default:'' }}">
|
|
</div>
|
|
<div class="col-md-2 d-flex align-items-end">
|
|
<button type="submit" class="btn btn-primary w-100">
|
|
<i class="fas fa-search"></i> {% trans "Search" %}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Staff List -->
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>{% trans "Name" %}</th>
|
|
<th>{% trans "Type" %}</th>
|
|
<th>{% trans "Job Title" %}</th>
|
|
<th>{% trans "Employee ID" %}</th>
|
|
<th>{% trans "Hospital" %}</th>
|
|
<th>{% trans "Department" %}</th>
|
|
<th>{% trans "User Account" %}</th>
|
|
<th>{% trans "Status" %}</th>
|
|
<th>{% trans "Actions" %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for staff_member in staff %}
|
|
<tr>
|
|
<td>
|
|
<strong>{{ staff_member.get_full_name }}</strong>
|
|
{% if staff_member.license_number %}
|
|
<br><small class="text-muted">{{ staff_member.license_number }}</small>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-info">{{ staff_member.get_staff_type_display }}</span>
|
|
</td>
|
|
<td>{{ staff_member.job_title }}</td>
|
|
<td>{{ staff_member.employee_id }}</td>
|
|
<td>{{ staff_member.hospital.name }}</td>
|
|
<td>{{ staff_member.department.name|default:"-" }}</td>
|
|
<td>
|
|
{% if staff_member.user %}
|
|
<span class="badge bg-success">
|
|
<i class="fas fa-check"></i> {% trans "Yes" %}
|
|
</span>
|
|
<br><small class="text-muted">{{ staff_member.user.username }}</small>
|
|
{% else %}
|
|
<span class="badge bg-danger">
|
|
<i class="fas fa-times"></i> {% trans "No" %}
|
|
</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if staff_member.status == 'active' %}
|
|
<span class="badge bg-success">{% trans "Active" %}</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">{% trans "Inactive" %}</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group">
|
|
<a href="{% url 'organizations:staff_detail' staff_member.id %}" class="btn btn-sm btn-outline-primary" title="{% trans 'View Details' %}">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
{% if user.is_px_admin or user.is_hospital_admin %}
|
|
{% if not staff_member.user and staff_member.email %}
|
|
<button type="button" class="btn btn-sm btn-outline-success" onclick="createUserAccount('{{ staff_member.id }}', '{{ staff_member.get_full_name }}')" title="{% trans 'Create User Account' %}">
|
|
<i class="fas fa-user-plus"></i>
|
|
</button>
|
|
{% endif %}
|
|
{% if staff_member.user %}
|
|
<button type="button" class="btn btn-sm btn-outline-warning" onclick="sendInvitation('{{ staff_member.id }}', '{{ staff_member.get_full_name }}')" title="{% trans 'Send Invitation Email' %}">
|
|
<i class="fas fa-envelope"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-sm btn-outline-danger" onclick="unlinkUserAccount('{{ staff_member.id }}', '{{ staff_member.get_full_name }}')" title="{% trans 'Unlink User Account' %}">
|
|
<i class="fas fa-user-minus"></i>
|
|
</button>
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="9" class="text-center py-5">
|
|
<i class="fas fa-users fa-3x text-muted mb-3"></i>
|
|
<p class="text-muted">{% trans "No staff members found" %}</p>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if page_obj.has_other_pages %}
|
|
<nav aria-label="Page navigation" class="mt-4">
|
|
<ul class="pagination justify-content-center">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page=1{% for key, value in filters.items %}{% if value %}&{{ key }}={{ value }}{% endif %}{% endfor %}">
|
|
<i class="fas fa-angle-double-left"></i>
|
|
</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% for key, value in filters.items %}{% if value %}&{{ key }}={{ value }}{% endif %}{% endfor %}">
|
|
<i class="fas fa-angle-left"></i>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
<li class="page-item active">
|
|
<span class="page-link">{{ page_obj.number }}</span>
|
|
</li>
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% for key, value in filters.items %}{% if value %}&{{ key }}={{ value }}{% endif %}{% endfor %}">
|
|
<i class="fas fa-angle-right"></i>
|
|
</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% for key, value in filters.items %}{% if value %}&{{ key }}={{ value }}{% endif %}{% endfor %}">
|
|
<i class="fas fa-angle-double-right"></i>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create User Modal -->
|
|
<div class="modal" id="createUserModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">{% trans "Create User Account" %}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>{% trans "Are you sure you want to create a user account for" %} <strong id="createUserName"></strong>?</p>
|
|
<p class="text-muted small">{% trans "A username will be generated automatically and credentials will be emailed to" %} <span id="createUserEmail"></span>.</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
|
<button type="button" class="btn btn-primary" onclick="confirmCreateUser()">{% trans "Create Account" %}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Send Invitation Modal -->
|
|
<div class="modal" id="sendInvitationModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">{% trans "Send Invitation Email" %}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>{% trans "Are you sure you want to send a new invitation email to" %} <strong id="sendInvitationName"></strong>?</p>
|
|
<p class="text-muted small">{% trans "A new password will be generated and sent to" %} <span id="sendInvitationEmail"></span>.</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
|
<button type="button" class="btn btn-primary" onclick="confirmSendInvitation()">{% trans "Send Email" %}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Unlink User Modal -->
|
|
<div class="modal" id="unlinkUserModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">{% trans "Unlink User Account" %}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>{% trans "Are you sure you want to unlink the user account from" %} <strong id="unlinkUserName"></strong>?</p>
|
|
<p class="text-warning small">{% trans "This will remove the login access for this staff member. The user account will still exist but will no longer be linked to this staff profile." %}</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
|
<button type="button" class="btn btn-danger" onclick="confirmUnlinkUser()">{% trans "Unlink Account" %}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
let currentStaffId = null;
|
|
let currentStaffName = null;
|
|
let currentStaffEmail = null;
|
|
|
|
function createUserAccount(staffId, staffName) {
|
|
currentStaffId = staffId;
|
|
currentStaffName = staffName;
|
|
|
|
// Get email from table
|
|
const row = event.target.closest('tr');
|
|
const emailSpan = row.querySelector('td:nth-child(7) small');
|
|
currentStaffEmail = emailSpan ? emailSpan.textContent : '';
|
|
|
|
document.getElementById('createUserName').textContent = staffName;
|
|
document.getElementById('createUserEmail').textContent = currentStaffEmail;
|
|
|
|
new bootstrap.Modal(document.getElementById('createUserModal')).show();
|
|
}
|
|
|
|
function confirmCreateUser() {
|
|
const btn = event.target;
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Creating...';
|
|
|
|
fetch(`/api/organizations/staff/${currentStaffId}/create_user_account/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
|
},
|
|
body: JSON.stringify({})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
bootstrap.Modal.getInstance(document.getElementById('createUserModal')).hide();
|
|
if (data.message) {
|
|
alert(data.message);
|
|
}
|
|
location.reload();
|
|
})
|
|
.catch(error => {
|
|
alert('Error: ' + error.message);
|
|
btn.disabled = false;
|
|
btn.innerHTML = 'Create Account';
|
|
});
|
|
}
|
|
|
|
function sendInvitation(staffId, staffName) {
|
|
currentStaffId = staffId;
|
|
currentStaffName = staffName;
|
|
|
|
document.getElementById('sendInvitationName').textContent = staffName;
|
|
|
|
new bootstrap.Modal(document.getElementById('sendInvitationModal')).show();
|
|
}
|
|
|
|
function confirmSendInvitation() {
|
|
const btn = event.target;
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sending...';
|
|
|
|
fetch(`/api/organizations/staff/${currentStaffId}/send_invitation/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
|
},
|
|
body: JSON.stringify({})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
bootstrap.Modal.getInstance(document.getElementById('sendInvitationModal')).hide();
|
|
if (data.message) {
|
|
alert(data.message);
|
|
}
|
|
location.reload();
|
|
})
|
|
.catch(error => {
|
|
alert('Error: ' + error.message);
|
|
btn.disabled = false;
|
|
btn.innerHTML = 'Send Email';
|
|
});
|
|
}
|
|
|
|
function unlinkUserAccount(staffId, staffName) {
|
|
currentStaffId = staffId;
|
|
currentStaffName = staffName;
|
|
|
|
document.getElementById('unlinkUserName').textContent = staffName;
|
|
|
|
new bootstrap.Modal(document.getElementById('unlinkUserModal')).show();
|
|
}
|
|
|
|
function confirmUnlinkUser() {
|
|
const btn = event.target;
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Unlinking...';
|
|
|
|
fetch(`/api/organizations/staff/${currentStaffId}/unlink_user/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
|
},
|
|
body: JSON.stringify({})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
bootstrap.Modal.getInstance(document.getElementById('unlinkUserModal')).hide();
|
|
if (data.message) {
|
|
alert(data.message);
|
|
}
|
|
location.reload();
|
|
})
|
|
.catch(error => {
|
|
alert('Error: ' + error.message);
|
|
btn.disabled = false;
|
|
btn.innerHTML = 'Unlink Account';
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %}
|