2025-10-03 20:11:25 +03:00

367 lines
11 KiB
HTML

{% load static %}
<div class="department-tree">
{% for department in departments %}
{% include 'hr/departments/department_tree_node.html' with department=department level=0 %}
{% empty %}
<div class="text-center text-muted py-4">
<i class="fas fa-building fa-3x mb-3"></i>
<p class="mb-0">No departments found</p>
<small>Create your first department to get started</small>
</div>
{% endfor %}
</div>
<!-- Department tree node template -->
{% verbatim %}
<script type="text/template" id="department-node-template">
<div class="department-node" data-department-id="{{id}}">
<div class="department-item d-flex align-items-center py-2 px-3 border-bottom">
<div class="department-toggle me-2" style="width: 20px;">
{{#if hasChildren}}
<i class="fas fa-chevron-right toggle-icon" data-bs-toggle="collapse"
data-bs-target="#dept-{{id}}-children" aria-expanded="false"></i>
{{/if}}
</div>
<div class="department-icon me-3">
<i class="fas {{icon}} text-{{color}}"></i>
</div>
<div class="department-info flex-grow-1">
<div class="d-flex align-items-center justify-content-between">
<div>
<h6 class="mb-0 department-name">
<a href="/hr/departments/{{id}}/"
class="text-decoration-none">{{name}}</a>
{{#unless is_active}}
<span class="badge badge-secondary ms-2">Inactive</span>
{{/unless}}
</h6>
<small class="text-muted">
{{department_type}} {{employee_count}} employees
{{#if department_head}}
Head: {{department_head}}
{{/if}}
</small>
</div>
<div class="department-actions">
<div class="btn-group btn-group-sm" role="group">
<a href="/hr/departments/{{id}}/"
class="btn btn-outline-primary btn-sm" title="View Details">
<i class="fas fa-eye"></i>
</a>
<a href="/hr/departments/{{id}}/edit/"
class="btn btn-outline-secondary btn-sm" title="Edit">
<i class="fas fa-edit"></i>
</a>
{{#if is_active}}
<button class="btn btn-outline-warning btn-sm"
onclick="deactivateDepartment('{{id}}')" title="Deactivate">
<i class="fas fa-pause"></i>
</button>
{{else}}
<button class="btn btn-outline-success btn-sm"
onclick="activateDepartment('{{id}}')" title="Activate">
<i class="fas fa-play"></i>
</button>
{{/if}}
</div>
</div>
</div>
</div>
</div>
{{#if hasChildren}}
<div class="collapse" id="dept-{{id}}-children">
<div class="department-children ps-4">
<!-- Children will be loaded here -->
</div>
</div>
{{/if}}
</div>
</script>
{% endverbatim %}
<style>
.department-tree {
background: #fff;
border-radius: 0.375rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.department-node {
border-left: 3px solid transparent;
transition: all 0.2s ease;
}
.department-node:hover {
background-color: #f8f9fa;
border-left-color: #007bff;
}
.department-node.active {
background-color: #e3f2fd;
border-left-color: #2196f3;
}
.department-item {
cursor: pointer;
transition: all 0.2s ease;
}
.department-toggle {
cursor: pointer;
user-select: none;
}
.toggle-icon {
transition: transform 0.2s ease;
color: #6c757d;
}
.toggle-icon:hover {
color: #495057;
}
.toggle-icon.expanded {
transform: rotate(90deg);
}
.department-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f8f9fa;
border-radius: 50%;
border: 2px solid #e9ecef;
}
.department-info .department-name a {
color: #212529;
font-weight: 500;
}
.department-info .department-name a:hover {
color: #007bff;
}
.department-actions {
opacity: 0;
transition: opacity 0.2s ease;
}
.department-node:hover .department-actions {
opacity: 1;
}
.department-children {
border-left: 1px dashed #dee2e6;
margin-left: 20px;
}
.department-children .department-node {
border-left-width: 2px;
}
.department-children .department-children .department-node {
border-left-width: 1px;
}
/* Department type colors */
.department-clinical { color: #dc3545; }
.department-administrative { color: #6f42c1; }
.department-support { color: #fd7e14; }
.department-ancillary { color: #20c997; }
.department-executive { color: #0d6efd; }
/* Responsive adjustments */
@media (max-width: 768px) {
.department-actions {
opacity: 1;
}
.department-actions .btn-group {
flex-direction: column;
}
.department-info small {
font-size: 0.75rem;
}
}
/* Loading states */
.department-loading {
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
color: #6c757d;
}
.department-loading .spinner-border {
width: 1.5rem;
height: 1.5rem;
margin-right: 0.5rem;
}
/* Empty state */
.department-tree-empty {
text-align: center;
padding: 3rem 1rem;
color: #6c757d;
}
.department-tree-empty .fa-building {
color: #dee2e6;
margin-bottom: 1rem;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize department tree functionality
initializeDepartmentTree();
});
function initializeDepartmentTree() {
// Handle toggle clicks
document.addEventListener('click', function(e) {
if (e.target.classList.contains('toggle-icon')) {
e.preventDefault();
e.stopPropagation();
const icon = e.target;
const targetId = icon.getAttribute('data-bs-target');
const target = document.querySelector(targetId);
if (target) {
// Toggle icon rotation
icon.classList.toggle('expanded');
// Load children if not already loaded
if (!target.hasAttribute('data-loaded')) {
loadDepartmentChildren(target, icon.getAttribute('data-department-id'));
}
}
}
});
// Handle department node clicks
document.addEventListener('click', function(e) {
if (e.target.closest('.department-item') && !e.target.closest('.department-actions')) {
const departmentItem = e.target.closest('.department-item');
const departmentNode = departmentItem.closest('.department-node');
// Remove active class from all nodes
document.querySelectorAll('.department-node.active').forEach(node => {
node.classList.remove('active');
});
// Add active class to clicked node
departmentNode.classList.add('active');
// Emit custom event for other components
const departmentId = departmentNode.getAttribute('data-department-id');
document.dispatchEvent(new CustomEvent('departmentSelected', {
detail: { departmentId: departmentId }
}));
}
});
}
function loadDepartmentChildren(container, departmentId) {
// Show loading state
container.innerHTML = '<div class="department-loading"><div class="spinner-border spinner-border-sm" role="status"></div>Loading...</div>';
// Make HTMX request to load children
htmx.ajax('GET', `/hr/api/departments/${departmentId}/children/`, {
target: container,
swap: 'innerHTML'
}).then(() => {
container.setAttribute('data-loaded', 'true');
}).catch(() => {
container.innerHTML = '<div class="text-danger p-2"><i class="fas fa-exclamation-triangle"></i> Failed to load children</div>';
});
}
function activateDepartment(departmentId) {
if (confirm('Are you sure you want to activate this department?')) {
htmx.ajax('POST', `/hr/departments/${departmentId}/activate/`, {
headers: {
'X-CSRFToken': getCsrfToken()
}
}).then(() => {
// Refresh the tree
refreshDepartmentTree();
showToast('Department activated successfully', 'success');
}).catch(() => {
showToast('Failed to activate department', 'error');
});
}
}
function deactivateDepartment(departmentId) {
if (confirm('Are you sure you want to deactivate this department? This will affect all associated employees and schedules.')) {
htmx.ajax('POST', `/hr/departments/${departmentId}/deactivate/`, {
headers: {
'X-CSRFToken': getCsrfToken()
}
}).then(() => {
// Refresh the tree
refreshDepartmentTree();
showToast('Department deactivated successfully', 'warning');
}).catch(() => {
showToast('Failed to deactivate department', 'error');
});
}
}
function refreshDepartmentTree() {
htmx.ajax('GET', '/hr/departments/tree/', {
target: '.department-tree',
swap: 'innerHTML'
});
}
function expandAllDepartments() {
document.querySelectorAll('.toggle-icon').forEach(icon => {
if (!icon.classList.contains('expanded')) {
icon.click();
}
});
}
function collapseAllDepartments() {
document.querySelectorAll('.toggle-icon.expanded').forEach(icon => {
icon.click();
});
}
function getCsrfToken() {
return document.querySelector('[name=csrfmiddlewaretoken]').value;
}
function showToast(message, type = 'info') {
// Use your existing toast notification system
if (typeof HospitalApp !== 'undefined' && HospitalApp.utils && HospitalApp.utils.showToast) {
HospitalApp.utils.showToast(message, type);
} else {
// Fallback to basic alert
alert(message);
}
}
// Export functions for external use
window.DepartmentTree = {
refresh: refreshDepartmentTree,
expandAll: expandAllDepartments,
collapseAll: collapseAllDepartments,
activate: activateDepartment,
deactivate: deactivateDepartment
};
</script>