HH/templates/organizations/staff_hierarchy.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 %}