355 lines
11 KiB
HTML
355 lines
11 KiB
HTML
{% extends "layouts/base.html" %}
|
|
|
|
{% load i18n %}
|
|
|
|
{% block title %}{% trans "Staff Hierarchy" %} - PX360{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.hierarchy-tree {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.hierarchy-node {
|
|
margin-left: 2rem;
|
|
position: relative;
|
|
}
|
|
|
|
.hierarchy-node::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: -1rem;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 2px;
|
|
background-color: #dee2e6;
|
|
}
|
|
|
|
.hierarchy-node::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: -1rem;
|
|
top: 1.5rem;
|
|
width: 1rem;
|
|
height: 2px;
|
|
background-color: #dee2e6;
|
|
}
|
|
|
|
.hierarchy-node:last-child::before {
|
|
height: 1.5rem;
|
|
}
|
|
|
|
.hierarchy-card {
|
|
border: 2px solid #dee2e6;
|
|
border-radius: 0.5rem;
|
|
padding: 1rem;
|
|
background: white;
|
|
transition: all 0.2s;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.hierarchy-card:hover {
|
|
border-color: #0d6efd;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.hierarchy-card.highlighted {
|
|
border-color: #ffc107;
|
|
background-color: #fff9db;
|
|
}
|
|
|
|
.hierarchy-card.search-result {
|
|
border-color: #28a745;
|
|
background-color: #d4edda;
|
|
box-shadow: 0 0 10px rgba(40, 167, 69, 0.3);
|
|
}
|
|
|
|
.staff-avatar {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 50%;
|
|
background-color: #e9ecef;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.25rem;
|
|
font-weight: bold;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.staff-info h6 {
|
|
margin-bottom: 0.25rem;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.staff-info small {
|
|
color: #6c757d;
|
|
}
|
|
|
|
.expand-icon {
|
|
font-size: 0.75rem;
|
|
cursor: pointer;
|
|
color: #6c757d;
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.expand-icon.rotated {
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
.direct-reports-badge {
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.children-container {
|
|
overflow: hidden;
|
|
transition: max-height 0.3s ease-out;
|
|
}
|
|
|
|
.children-container.collapsed {
|
|
max-height: 0;
|
|
}
|
|
|
|
.tree-connector {
|
|
position: absolute;
|
|
left: -1rem;
|
|
top: 1.5rem;
|
|
bottom: 0;
|
|
width: 2px;
|
|
background-color: #dee2e6;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h1 class="page-title">{% trans "Staff Hierarchy" %}</h1>
|
|
<p class="text-muted">{% trans "View organizational structure and reporting relationships" %}</p>
|
|
</div>
|
|
<div class="btn-group">
|
|
<a href="{% url 'organizations:staff_list' %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-list"></i> {% trans "List View" %}
|
|
</a>
|
|
{% 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>
|
|
</div>
|
|
|
|
<!-- Statistics -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-users fa-2x text-primary"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h6 class="text-muted mb-0">{% trans "Total Staff" %}</h6>
|
|
<h3 class="mb-0">{{ total_staff }}</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-user-tie fa-2x text-success"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h6 class="text-muted mb-0">{% trans "Top Managers" %}</h6>
|
|
<h3 class="mb-0">{{ top_managers }}</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-sitemap fa-2x text-info"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h6 class="text-muted mb-0">{% trans "Hierarchy Levels" %}</h6>
|
|
<h3 class="mb-0">{% trans "Multi-level" %}</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</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-3">
|
|
<label class="form-label">{% trans "Department" %}</label>
|
|
<select name="department" class="form-select">
|
|
<option value="">{% trans "All Departments" %}</option>
|
|
{% for department in departments %}
|
|
<option value="{{ department.id }}" {% if request.GET.department == department.id|stringformat:"s" %}selected{% endif %}>
|
|
{{ department.name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">{% trans "Search Staff" %}</label>
|
|
<div class="input-group">
|
|
<input type="text" name="search" class="form-control" placeholder="{% trans 'Search by name or employee ID...' %}" value="{{ request.GET.search|default:'' }}">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-search"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2 d-flex align-items-end">
|
|
<a href="{% url 'organizations:staff_hierarchy' %}" class="btn btn-outline-secondary w-100">
|
|
<i class="fas fa-times"></i> {% trans "Clear" %}
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Search Result Message -->
|
|
{% if search_result %}
|
|
<div class="alert alert-info alert-dismissible fade show" role="alert">
|
|
<i class="fas fa-search me-2"></i>
|
|
{% trans "Found staff member:" %} <strong>{{ search_result.get_full_name }}</strong>
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Hierarchy Tree -->
|
|
{% if hierarchy %}
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h5 class="mb-0">{% trans "Organizational Structure" %}</h5>
|
|
<button type="button" class="btn btn-sm btn-outline-primary" onclick="expandAll()">
|
|
<i class="fas fa-expand"></i> {% trans "Expand All" %}
|
|
</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="collapseAll()">
|
|
<i class="fas fa-compress"></i> {% trans "Collapse All" %}
|
|
</button>
|
|
</div>
|
|
|
|
<div class="hierarchy-tree" id="hierarchyTree">
|
|
{% for node in hierarchy %}
|
|
{% include 'organizations/hierarchy_node.html' %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="card">
|
|
<div class="card-body text-center py-5">
|
|
<i class="fas fa-sitemap fa-4x text-muted mb-3"></i>
|
|
<h4 class="text-muted">{% trans "No Staff Hierarchy Found" %}</h4>
|
|
<p class="text-muted">{% trans "There are no staff members with reporting relationships in the selected filters." %}</p>
|
|
{% if user.is_px_admin or user.is_hospital_admin %}
|
|
<a href="{% url 'organizations:staff_create' %}" class="btn btn-primary mt-3">
|
|
<i class="fas fa-plus"></i> {% trans "Add Staff Member" %}
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
// Toggle node expansion
|
|
function toggleNode(nodeId) {
|
|
const childrenContainer = document.getElementById(`children-${nodeId}`);
|
|
const expandIcon = document.getElementById(`expand-${nodeId}`);
|
|
|
|
if (childrenContainer) {
|
|
childrenContainer.classList.toggle('collapsed');
|
|
if (expandIcon) {
|
|
expandIcon.classList.toggle('rotated');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Expand all nodes
|
|
function expandAll() {
|
|
document.querySelectorAll('.children-container').forEach(container => {
|
|
container.classList.remove('collapsed');
|
|
});
|
|
document.querySelectorAll('.expand-icon').forEach(icon => {
|
|
icon.classList.remove('rotated');
|
|
});
|
|
}
|
|
|
|
// Collapse all nodes
|
|
function collapseAll() {
|
|
document.querySelectorAll('.children-container').forEach(container => {
|
|
container.classList.add('collapsed');
|
|
});
|
|
document.querySelectorAll('.expand-icon').forEach(icon => {
|
|
icon.classList.add('rotated');
|
|
});
|
|
}
|
|
|
|
// Scroll to search result
|
|
{% if search_result %}
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const searchResult = document.querySelector('.hierarchy-card.search-result');
|
|
if (searchResult) {
|
|
searchResult.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
|
|
// Expand all parent nodes
|
|
let parent = searchResult.closest('.hierarchy-node');
|
|
while (parent) {
|
|
const childrenContainer = parent.parentElement.closest('.children-container');
|
|
if (childrenContainer) {
|
|
childrenContainer.classList.remove('collapsed');
|
|
}
|
|
const expandIcon = parent.querySelector('.expand-icon');
|
|
if (expandIcon) {
|
|
expandIcon.classList.remove('rotated');
|
|
}
|
|
parent = parent.parentElement.closest('.hierarchy-node');
|
|
}
|
|
|
|
// Highlight animation
|
|
setTimeout(() => {
|
|
searchResult.style.boxShadow = '0 0 20px rgba(40, 167, 69, 0.5)';
|
|
}, 100);
|
|
|
|
setTimeout(() => {
|
|
searchResult.style.boxShadow = '';
|
|
}, 2000);
|
|
}
|
|
});
|
|
{% endif %}
|
|
</script>
|
|
{% endblock %}
|