775 lines
42 KiB
HTML
775 lines
42 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Departments{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Breadcrumb -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="page-title-box d-sm-flex align-items-center justify-content-between">
|
|
<h4 class="mb-sm-0">Departments</h4>
|
|
<div class="page-title-right">
|
|
<ol class="breadcrumb m-0">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item active">Departments</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters and Actions -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="d-flex flex-wrap gap-3">
|
|
<!-- Search Form -->
|
|
<div class="flex-grow-1">
|
|
<form method="get" class="d-flex gap-2">
|
|
<div class="input-group">
|
|
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
|
<input type="text" class="form-control" name="search" placeholder="Search departments..." value="{{ request.GET.search|default:'' }}">
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">Search</button>
|
|
{% if request.GET.search or request.GET.status or request.GET.type %}
|
|
<a href="{% url 'core:department_list' %}" class="btn btn-outline-secondary">Clear</a>
|
|
{% endif %}
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="d-flex gap-2">
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-primary dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class="fas fa-filter me-1"></i>
|
|
Filter
|
|
</button>
|
|
<div class="dropdown-menu p-3" style="min-width: 250px;">
|
|
<form method="get">
|
|
<div class="mb-3">
|
|
<label class="form-label">Status</label>
|
|
<select name="status" class="form-select">
|
|
<option value="">All</option>
|
|
<option value="active" {% if request.GET.status == 'active' %}selected{% endif %}>Active</option>
|
|
<option value="inactive" {% if request.GET.status == 'inactive' %}selected{% endif %}>Inactive</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Type</label>
|
|
<select name="type" class="form-select">
|
|
<option value="">All</option>
|
|
<option value="clinical" {% if request.GET.type == 'clinical' %}selected{% endif %}>Clinical</option>
|
|
<option value="administrative" {% if request.GET.type == 'administrative' %}selected{% endif %}>Administrative</option>
|
|
<option value="support" {% if request.GET.type == 'support' %}selected{% endif %}>Support</option>
|
|
</select>
|
|
</div>
|
|
<div class="d-grid">
|
|
<button type="submit" class="btn btn-primary">Apply Filters</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-primary dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class="fas fa-sort me-1"></i>
|
|
Sort
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="?{% if request.GET.search %}search={{ request.GET.search }}&{% endif %}sort=name">Name (A-Z)</a></li>
|
|
<li><a class="dropdown-item" href="?{% if request.GET.search %}search={{ request.GET.search }}&{% endif %}sort=-name">Name (Z-A)</a></li>
|
|
<li><a class="dropdown-item" href="?{% if request.GET.search %}search={{ request.GET.search }}&{% endif %}sort=type">Type</a></li>
|
|
<li><a class="dropdown-item" href="?{% if request.GET.search %}search={{ request.GET.search }}&{% endif %}sort=created_at">Created (Oldest)</a></li>
|
|
<li><a class="dropdown-item" href="?{% if request.GET.search %}search={{ request.GET.search }}&{% endif %}sort=-created_at">Created (Newest)</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-primary dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class="fas fa-cog me-1"></i>
|
|
Actions
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" onclick="bulkActivate()">Activate Selected</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="bulkDeactivate()">Deactivate Selected</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item" href="#" onclick="exportDepartments()">Export Departments</a></li>
|
|
<li><a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#importModal">Import Departments</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<a href="{% url 'core:department_create' %}" class="btn btn-success">
|
|
<i class="fas fa-plus me-1"></i>
|
|
New Department
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- View Toggle -->
|
|
<div class="row mb-3">
|
|
<div class="col-12">
|
|
<div class="btn-group" role="group">
|
|
<input type="radio" class="btn-check" name="viewMode" id="tableView" autocomplete="off" checked>
|
|
<label class="btn btn-outline-primary" for="tableView">
|
|
<i class="fas fa-table me-1"></i>
|
|
Table View
|
|
</label>
|
|
|
|
<input type="radio" class="btn-check" name="viewMode" id="cardView" autocomplete="off">
|
|
<label class="btn btn-outline-primary" for="cardView">
|
|
<i class="fas fa-th-large me-1"></i>
|
|
Card View
|
|
</label>
|
|
|
|
<input type="radio" class="btn-check" name="viewMode" id="orgChartView" autocomplete="off">
|
|
<label class="btn btn-outline-primary" for="orgChartView">
|
|
<i class="fas fa-sitemap me-1"></i>
|
|
Org Chart
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table View -->
|
|
<div class="row" id="tableViewContent">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<form id="bulkActionForm" method="post" action="{% url 'core:bulk_activate_departments' %}">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" id="bulkAction" value="">
|
|
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th style="width: 30px;">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="selectAll">
|
|
</div>
|
|
</th>
|
|
<th>Department</th>
|
|
<th>Type</th>
|
|
<th>Head</th>
|
|
<th>Staff Count</th>
|
|
<th>Status</th>
|
|
<th style="width: 150px;">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for department in departments %}
|
|
<tr>
|
|
<td>
|
|
<div class="form-check">
|
|
<input class="form-check-input department-checkbox" type="checkbox" name="selected_ids" value="{{ department.pk }}">
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="avatar-xs me-2">
|
|
<span class="avatar-title rounded-circle bg-primary text-white">
|
|
{{ department.name|first|upper }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<a href="{% url 'core:department_detail' department.pk %}" class="text-reset fw-medium">{{ department.name }}</a>
|
|
<p class="text-muted mb-0 small">{{ department.code }}</p>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if department.department_type == 'clinical' %}success{% elif department.department_type == 'administrative' %}info{% elif department.department_type == 'support' %}secondary{% endif %}">
|
|
{{ department.get_department_type_display }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
{% if department.department_head %}
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="avatar-xs me-2">
|
|
<span class="avatar-title rounded-circle bg-secondary text-white">
|
|
{{ department.department_head.user.first_name|first|upper }}{{ department.department_head.user.last_name|first|upper }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<span>{{ department.department_head.user.get_full_name }}</span>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<span class="text-muted">Not assigned</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-info">{{ department.staff_count }}</span>
|
|
</td>
|
|
<td>
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" role="switch"
|
|
id="status-{{ department.pk }}"
|
|
{% if department.is_active %}checked{% endif %}
|
|
onchange="toggleDepartmentStatus('{{ department.pk }}', this.checked)">
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="dropdown">
|
|
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
|
Actions
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="{% url 'core:department_detail' department.pk %}">
|
|
<i class="fas fa-eye me-2"></i>View
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="{% url 'core:department_update' department.pk %}">
|
|
<i class="fas fa-edit me-2"></i>Edit
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="{% url 'core:department_delete' department.pk %}">
|
|
<i class="fas fa-trash me-2"></i>Delete
|
|
</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item" href="{% url 'hr:employee_list' %}?department={{ department.pk }}">
|
|
<i class="fas fa-users me-2"></i>View Staff
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="7" class="text-center py-4">
|
|
<div class="d-flex flex-column align-items-center">
|
|
<i class="fas fa-building fa-3x text-muted mb-3"></i>
|
|
<h5>No departments found</h5>
|
|
<p class="text-muted">No departments match your criteria.</p>
|
|
<a href="{% url 'core:department_create' %}" class="btn btn-primary mt-2">
|
|
<i class="fas fa-plus me-1"></i>
|
|
Create New Department
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
<div class="d-flex justify-content-between align-items-center mt-3">
|
|
<div>
|
|
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ paginator.count }} departments
|
|
</div>
|
|
<nav>
|
|
<ul class="pagination mb-0">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page=1{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.type %}&type={{ request.GET.type }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">
|
|
<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 }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.type %}&type={{ request.GET.type }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">
|
|
<i class="fas fa-angle-left"></i>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for num in paginator.page_range %}
|
|
{% if page_obj.number == num %}
|
|
<li class="page-item active">
|
|
<span class="page-link">{{ num }}</span>
|
|
</li>
|
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ num }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.type %}&type={{ request.GET.type }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">{{ num }}</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.type %}&type={{ request.GET.type }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">
|
|
<i class="fas fa-angle-right"></i>
|
|
</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ paginator.num_pages }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.type %}&type={{ request.GET.type }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">
|
|
<i class="fas fa-angle-double-right"></i>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Card View -->
|
|
<div class="row" id="cardViewContent" style="display: none;">
|
|
{% for department in departments %}
|
|
<div class="col-md-6 col-lg-4 col-xl-3">
|
|
<div class="card">
|
|
<div class="card-header bg-{% if department.department_type == 'clinical' %}success{% elif department.department_type == 'administrative' %}info{% elif department.department_type == 'support' %}secondary{% endif %} bg-opacity-25">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="avatar-sm me-2">
|
|
<span class="avatar-title rounded-circle bg-primary text-white">
|
|
{{ department.name|first|upper }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<h5 class="card-title mb-0">{{ department.name }}</h5>
|
|
<small class="text-muted">{{ department.code }}</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<span class="badge bg-{% if department.department_type == 'clinical' %}success{% elif department.department_type == 'administrative' %}info{% elif department.department_type == 'support' %}secondary{% endif %}">
|
|
{{ department.get_department_type_display }}
|
|
</span>
|
|
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" role="switch"
|
|
id="card-status-{{ department.pk }}"
|
|
{% if department.is_active %}checked{% endif %}
|
|
onchange="toggleDepartmentStatus('{{ department.pk }}', this.checked)">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<h6 class="text-muted mb-2">Department Head</h6>
|
|
{% if department.department_head %}
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<div class="avatar-xs me-2">
|
|
<span class="avatar-title rounded-circle bg-secondary text-white">
|
|
{{ department.department_head.user.first_name|first|upper }}{{ department.department_head.user.last_name|first|upper }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<span>{{ department.department_head.user.get_full_name }}</span>
|
|
<small class="d-block text-muted">{{ department.department_head.designation }}</small>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-muted mb-0">Not assigned</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<h6 class="text-muted mb-2">Staff</h6>
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-users text-info me-2"></i>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<span>{{ department.staff_count }} members</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-grid gap-2">
|
|
<a href="{% url 'core:department_detail' department.pk %}" class="btn btn-sm btn-outline-primary">
|
|
<i class="fas fa-eye me-1"></i>
|
|
View Details
|
|
</a>
|
|
<div class="btn-group" role="group">
|
|
<a href="{% url 'core:department_update' department.pk %}" class="btn btn-sm btn-outline-secondary">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
<a href="{% url 'hr:employee_list' %}?department={{ department.pk }}" class="btn btn-sm btn-outline-secondary">
|
|
<i class="fas fa-users"></i>
|
|
</a>
|
|
<a href="{% url 'core:department_delete' department.pk %}" class="btn btn-sm btn-outline-danger">
|
|
<i class="fas fa-trash"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer text-muted">
|
|
<small>Created: {{ department.created_at|date:"M d, Y" }}</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-body text-center py-5">
|
|
<i class="fas fa-building fa-3x text-muted mb-3"></i>
|
|
<h5>No departments found</h5>
|
|
<p class="text-muted">No departments match your criteria.</p>
|
|
<a href="{% url 'core:department_create' %}" class="btn btn-primary mt-2">
|
|
<i class="fas fa-plus me-1"></i>
|
|
Create New Department
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<!-- Pagination for Card View -->
|
|
{% if is_paginated %}
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ paginator.count }} departments
|
|
</div>
|
|
<nav>
|
|
<ul class="pagination mb-0">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page=1{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.type %}&type={{ request.GET.type }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">
|
|
<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 }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.type %}&type={{ request.GET.type }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">
|
|
<i class="fas fa-angle-left"></i>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for num in paginator.page_range %}
|
|
{% if page_obj.number == num %}
|
|
<li class="page-item active">
|
|
<span class="page-link">{{ num }}</span>
|
|
</li>
|
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ num }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.type %}&type={{ request.GET.type }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">{{ num }}</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.type %}&type={{ request.GET.type }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">
|
|
<i class="fas fa-angle-right"></i>
|
|
</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ paginator.num_pages }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.type %}&type={{ request.GET.type }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">
|
|
<i class="fas fa-angle-double-right"></i>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Org Chart View -->
|
|
<div class="row" id="orgChartViewContent" style="display: none;">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div id="orgChart" class="text-center">
|
|
<div class="d-flex justify-content-center">
|
|
<div class="org-chart-node org-chart-root mb-4">
|
|
<div class="org-chart-node-content">
|
|
<h5 class="mb-0">{{ organization_name }}</h5>
|
|
<small>Organization</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="org-chart-connector"></div>
|
|
|
|
<div class="row justify-content-center">
|
|
{% for dept_type, type_departments in department_types.items %}
|
|
<div class="col-md-4 mb-4">
|
|
<div class="org-chart-node org-chart-department-type mb-3">
|
|
<div class="org-chart-node-content">
|
|
<h6 class="mb-0">{{ dept_type|title }} Departments</h6>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="org-chart-connector"></div>
|
|
|
|
<div class="d-flex flex-column align-items-center">
|
|
{% for department in type_departments %}
|
|
<div class="org-chart-node org-chart-department mb-2">
|
|
<div class="org-chart-node-content">
|
|
<a href="{% url 'core:department_detail' department.pk %}" class="text-reset">
|
|
<h6 class="mb-0">{{ department.name }}</h6>
|
|
<small>{{ department.staff_count }} staff</small>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="org-chart-node org-chart-empty">
|
|
<div class="org-chart-node-content">
|
|
<p class="mb-0">No departments</p>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Import Modal -->
|
|
<div class="modal fade" id="importModal" tabindex="-1" aria-labelledby="importModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="importModalLabel">Import Departments</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="importForm" method="post" action="" enctype="multipart/form-data">
|
|
{% csrf_token %}
|
|
|
|
<div class="mb-3">
|
|
<label for="importFile" class="form-label">CSV File</label>
|
|
<input type="file" class="form-control" id="importFile" name="import_file" accept=".csv" required>
|
|
<div class="form-text">File must be in CSV format with headers: name, code, type, description, is_active</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="updateExisting" name="update_existing">
|
|
<label class="form-check-label" for="updateExisting">
|
|
Update existing departments
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="alert alert-info">
|
|
<div class="d-flex">
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-info-circle"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<p class="mb-0">Need a template? <a href="" class="alert-link">Download CSV template</a></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-primary" onclick="document.getElementById('importForm').submit()">Import</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block css %}
|
|
<style>
|
|
/* Org Chart Styles */
|
|
.org-chart-node {
|
|
display: inline-block;
|
|
border-radius: 8px;
|
|
padding: 10px;
|
|
min-width: 200px;
|
|
}
|
|
|
|
.org-chart-node-content {
|
|
padding: 10px;
|
|
}
|
|
|
|
.org-chart-root {
|
|
background-color: #4e73df;
|
|
color: white;
|
|
}
|
|
|
|
.org-chart-department-type {
|
|
background-color: #1cc88a;
|
|
color: white;
|
|
}
|
|
|
|
.org-chart-department {
|
|
background-color: #f8f9fc;
|
|
border: 1px solid #e3e6f0;
|
|
}
|
|
|
|
.org-chart-empty {
|
|
background-color: #f8f9fc;
|
|
border: 1px dashed #e3e6f0;
|
|
color: #858796;
|
|
}
|
|
|
|
.org-chart-connector {
|
|
height: 20px;
|
|
border-left: 2px solid #e3e6f0;
|
|
margin: 0 auto;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// View toggle
|
|
const tableView = document.getElementById('tableView');
|
|
const cardView = document.getElementById('cardView');
|
|
const orgChartView = document.getElementById('orgChartView');
|
|
const tableViewContent = document.getElementById('tableViewContent');
|
|
const cardViewContent = document.getElementById('cardViewContent');
|
|
const orgChartViewContent = document.getElementById('orgChartViewContent');
|
|
|
|
tableView.addEventListener('change', function() {
|
|
if (this.checked) {
|
|
tableViewContent.style.display = 'flex';
|
|
cardViewContent.style.display = 'none';
|
|
orgChartViewContent.style.display = 'none';
|
|
localStorage.setItem('departmentViewMode', 'table');
|
|
}
|
|
});
|
|
|
|
cardView.addEventListener('change', function() {
|
|
if (this.checked) {
|
|
tableViewContent.style.display = 'none';
|
|
cardViewContent.style.display = 'flex';
|
|
orgChartViewContent.style.display = 'none';
|
|
localStorage.setItem('departmentViewMode', 'card');
|
|
}
|
|
});
|
|
|
|
orgChartView.addEventListener('change', function() {
|
|
if (this.checked) {
|
|
tableViewContent.style.display = 'none';
|
|
cardViewContent.style.display = 'none';
|
|
orgChartViewContent.style.display = 'flex';
|
|
localStorage.setItem('departmentViewMode', 'orgChart');
|
|
}
|
|
});
|
|
|
|
// Load saved view preference
|
|
const savedViewMode = localStorage.getItem('departmentViewMode');
|
|
if (savedViewMode === 'card') {
|
|
cardView.checked = true;
|
|
tableViewContent.style.display = 'none';
|
|
cardViewContent.style.display = 'flex';
|
|
orgChartViewContent.style.display = 'none';
|
|
} else if (savedViewMode === 'orgChart') {
|
|
orgChartView.checked = true;
|
|
tableViewContent.style.display = 'none';
|
|
cardViewContent.style.display = 'none';
|
|
orgChartViewContent.style.display = 'flex';
|
|
} else {
|
|
tableView.checked = true;
|
|
}
|
|
|
|
// Select all checkboxes
|
|
const selectAll = document.getElementById('selectAll');
|
|
const checkboxes = document.querySelectorAll('.department-checkbox');
|
|
|
|
selectAll.addEventListener('change', function() {
|
|
checkboxes.forEach(checkbox => {
|
|
checkbox.checked = this.checked;
|
|
});
|
|
});
|
|
|
|
checkboxes.forEach(checkbox => {
|
|
checkbox.addEventListener('change', function() {
|
|
const allChecked = Array.from(checkboxes).every(c => c.checked);
|
|
const anyChecked = Array.from(checkboxes).some(c => c.checked);
|
|
|
|
selectAll.checked = allChecked;
|
|
selectAll.indeterminate = anyChecked && !allChecked;
|
|
});
|
|
});
|
|
});
|
|
|
|
function toggleDepartmentStatus(departmentId, isActive) {
|
|
fetch(`{% url 'core:activate_department' 0 %}`.replace('0', departmentId), {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': '{{ csrf_token }}'
|
|
},
|
|
body: JSON.stringify({ is_active: isActive })
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('Network response was not ok');
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
// Sync both table and card view switches
|
|
const tableSwitch = document.getElementById(`status-${departmentId}`);
|
|
const cardSwitch = document.getElementById(`card-status-${departmentId}`);
|
|
|
|
if (tableSwitch) tableSwitch.checked = data.is_active;
|
|
if (cardSwitch) cardSwitch.checked = data.is_active;
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Failed to update department status. Please try again.');
|
|
});
|
|
}
|
|
|
|
function bulkActivate() {
|
|
if (confirmBulkAction('activate')) {
|
|
document.getElementById('bulkAction').value = 'activate';
|
|
document.getElementById('bulkActionForm').submit();
|
|
}
|
|
}
|
|
|
|
function bulkDeactivate() {
|
|
if (confirmBulkAction('deactivate')) {
|
|
document.getElementById('bulkAction').value = 'deactivate';
|
|
document.getElementById('bulkActionForm').submit();
|
|
}
|
|
}
|
|
|
|
function confirmBulkAction(action) {
|
|
const checkboxes = document.querySelectorAll('.department-checkbox:checked');
|
|
|
|
if (checkboxes.length === 0) {
|
|
alert('Please select at least one department.');
|
|
return false;
|
|
}
|
|
|
|
let message = '';
|
|
switch (action) {
|
|
case 'activate':
|
|
message = `Are you sure you want to activate ${checkboxes.length} department(s)?`;
|
|
break;
|
|
case 'deactivate':
|
|
message = `Are you sure you want to deactivate ${checkboxes.length} department(s)?`;
|
|
break;
|
|
}
|
|
|
|
return confirm(message);
|
|
}
|
|
|
|
{#function exportDepartments() {#}
|
|
{# window.open('{% url "core:export_departments" %}', '_blank');#}
|
|
{# }#}
|
|
</script>
|
|
{% endblock %}
|
|
|