Files
ACORE/templates/index.html
T
lokses bfd67241c4 AD office-only scope, mapping page, auto-rebuild of directory tree
- ad_connector: restrict provisioning to office employees
  (user_location_type='office') in sync service, signals and command;
  disable AD account on transfer out of office, re-enable on return
- ad_connector: /ad-mapping/ page (staff only) — match office employees
  to AD accounts with suggestions by email/login/full name, link/unlink/
  create actions, list of unmatched domain accounts; sidebar nav item
- core: auto-rebuild treenode Directory tree (_path/_depth) after
  import-export imports and post_migrate; "rebuild tree" button in
  DirectoryAdmin and rebuild_directory_tree management command

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 16:48:38 +03:00

342 lines
18 KiB
HTML

{% load static %}
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>АКС HR — Портал управління</title>
<link rel="stylesheet" href="{% static 'assets/css/main.css' %}">
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<script>htmx.config.allowEval = false;</script>
<script src="{% static 'assets/js/pharmacies.js' %}"></script>
<script src="{% static 'assets/js/employees.js' %}"></script>
<script src="{% static 'assets/js/structure.js' %}"></script>
<script src="{% static 'assets/js/timetrack.js' %}"></script>
</head>
<body>
<script>
function toggleSidebar() {
const s = document.getElementById('sidebar');
s.classList.toggle('collapsed');
}
function setActive(el) {
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
el.classList.add('active');
}
</script>
<!-- Loader bar (HTMX) -->
<div class="loader-bar htmx-indicator" id="loader"></div>
<div style="display:flex; height:100vh; overflow:hidden;">
<!-- ══════════════════ SIDEBAR ══════════════════ -->
<aside id="sidebar" class="sidebar">
<!-- Шапка -->
<div class="sidebar-header">
<div class="sidebar-logo">HR</div>
<div class="sidebar-logo-text">
<div style="color:var(--color-sidebar-accent-foreground); font-size:0.875rem; font-weight:500; line-height:1.2;">АКС HR</div>
<div style="color:var(--color-sidebar-foreground); font-size:0.6875rem; opacity:0.6; line-height:1.2;">Портал управління</div>
</div>
</div>
<!-- Навигация -->
<nav style="flex:1; overflow-y:auto; overflow-x:hidden; padding:0.5rem; display:flex; flex-direction:column; gap:1rem;">
<!-- Основное -->
<div>
<div class="nav-section-label">Основне</div>
<div style="display:flex; flex-direction:column; gap:2px;">
<a href="#" class="nav-item active"
hx-get="/dashboard/"
hx-target="#main-content"
hx-push-url="/dashboard/"
hx-indicator="#loader"
onclick="setActive(this)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/>
<rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/>
</svg>
<span class="nav-label-text">Дашборд</span>
</a>
<a href="#" class="nav-item"
hx-get="/employees/"
hx-target="#main-content"
hx-push-url="/employees/"
hx-indicator="#loader"
onclick="setActive(this)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
<path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>
</svg>
<span class="nav-label-text">Співробітники</span>
</a>
<a href="#" class="nav-item"
hx-get="/structure/"
hx-target="#main-content"
hx-push-url="/structure/"
hx-indicator="#loader"
onclick="setActive(this)">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M2 4h12M2 8h8M2 12h5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
<span class="nav-label-text">Структура</span>
</a>
<a href="#" class="nav-item"
hx-get="/pharmacies/"
hx-target="#main-content"
hx-push-url="/pharmacies/"
hx-indicator="#loader"
onclick="setActive(this)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14.106 5.553a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619v12.764a1 1 0 0 1-.553.894l-4.553 2.277a2 2 0 0 1-1.788 0l-4.212-2.106a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0z"/>
<path d="M15 5.764v15"/><path d="M9 3.236v15"/>
</svg>
<span class="nav-label-text">Аптеки</span>
<span class="nav-badge nav-badge-amber">14</span>
</a>
<a href="#" class="nav-item"
hx-get="/recruiting/"
hx-target="#main-content"
hx-push-url="/recruiting/"
hx-indicator="#loader"
onclick="setActive(this)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
<line x1="19" x2="19" y1="8" y2="14"/><line x1="22" x2="16" y1="11" y2="11"/>
</svg>
<span class="nav-label-text">Рекрутинг</span>
<span class="nav-badge nav-badge-red">27</span>
</a>
<a href="#" class="nav-item"
hx-get="/timetrack/"
hx-target="#main-content"
hx-push-url="/timetrack/"
hx-indicator="#loader"
onclick="setActive(this)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>
</svg>
<span class="nav-label-text">Облік робочого часу</span>
</a>
</div>
</div>
<!-- Инструменты -->
<div>
<div class="nav-section-label">Інструменти</div>
<div style="display:flex; flex-direction:column; gap:2px;">
<a href="#" class="nav-item"
hx-get="/calendar/"
hx-target="#main-content"
hx-push-url="/calendar/"
hx-indicator="#loader"
onclick="setActive(this)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect width="18" height="18" x="3" y="4" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/>
</svg>
<span class="nav-label-text">Календар</span>
</a>
<a href="#" class="nav-item"
hx-get="/reports/"
hx-target="#main-content"
hx-push-url="/reports/"
hx-indicator="#loader"
onclick="setActive(this)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 3v18h18"/><path d="m19 9-5 5-4-4-3 3"/>
</svg>
<span class="nav-label-text">Звіти</span>
</a>
<a href="#" class="nav-item"
hx-get="/documents/"
hx-target="#main-content"
hx-push-url="/documents/"
hx-indicator="#loader"
onclick="setActive(this)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/>
<path d="M14 2v4a2 2 0 0 0 2 2h4M10 9H8M16 13H8M16 17H8"/>
</svg>
<span class="nav-label-text">Документи</span>
</a>
{% if request.user.is_staff %}
<a href="#" class="nav-item"
hx-get="/ad-mapping/"
hx-target="#main-content"
hx-push-url="/ad-mapping/"
hx-indicator="#loader"
onclick="setActive(this)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect width="8" height="8" x="2" y="2" rx="2"/><rect width="8" height="8" x="14" y="14" rx="2"/>
<path d="M10 6h4a2 2 0 0 1 2 2v2M14 18h-4a2 2 0 0 1-2-2v-2"/>
</svg>
<span class="nav-label-text">Active Directory</span>
</a>
{% endif %}
<a href="#" class="nav-item"
hx-get="/settings/"
hx-target="#main-content"
hx-push-url="/settings/"
hx-indicator="#loader"
onclick="setActive(this)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
<circle cx="12" cy="12" r="3"/>
</svg>
<span class="nav-label-text">Налаштування</span>
</a>
</div>
</div>
</nav>
<!-- Футер сайдбара -->
<div class="sidebar-footer">
<div style="width:1.75rem; height:1.75rem; border-radius:50%; background:var(--color-sidebar-primary); display:flex; align-items:center; justify-content:center; color:var(--color-sidebar-primary-foreground); font-size:0.6875rem; font-weight:600; flex-shrink:0;">
АК
</div>
<div class="sidebar-logo-text" style="min-width:0; flex:1;">
<div style="color:var(--color-sidebar-accent-foreground); font-size:0.75rem; font-weight:500; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">{{ request.user.username }}</div>
<div style="color:var(--color-sidebar-foreground); font-size:0.6rem; opacity:0.6; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">{{ request.user.email }}</div>
</div>
<div class="footer-actions">
<a href="{% url 'logout' %}" class="btn btn-ghost btn-icon" title="Вийти" style="color:var(--color-sidebar-foreground); opacity:0.8; display:flex; align-items:center; justify-content:center;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
<polyline points="16 17 21 12 16 7"/><line x1="21" x2="9" y1="12" y2="12"/>
</svg>
</a>
</div>
</div>
</aside>
<!-- ══════════════════ MAIN ══════════════════ -->
<div style="flex:1; display:flex; flex-direction:column; min-width:0; height:100vh;">
<!-- Header -->
<header style="height:56px; display:flex; align-items:center; justify-content:space-between; border-bottom:1px solid var(--color-border); background:var(--color-card); padding:0 1.25rem; flex-shrink:0; gap:1rem;">
<div style="display:flex; align-items:center; gap:0.75rem; flex:1;">
<!-- Кнопка сворачивания -->
<button onclick="toggleSidebar()" class="btn btn-ghost btn-icon" title="Згорнути меню">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/>
</svg>
</button>
<!-- Поиск -->
<div style="display:flex; align-items:center; gap:0.5rem; background:var(--color-muted); border:1px solid var(--color-border); border-radius:var(--radius); padding:0.4375rem 0.75rem; width:18rem;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:var(--color-muted-foreground); flex-shrink:0;">
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
</svg>
<input type="text"
placeholder="Пошук співробітників, аптек..."
class="input"
style="border:none; background:transparent; padding:0; box-shadow:none;"
hx-get="/api/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#search-results"
name="q">
</div>
</div>
<div style="display:flex; align-items:center; gap:0.5rem;">
<!-- Уведомления -->
<button class="btn btn-ghost btn-icon" style="position:relative;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/>
<path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/>
</svg>
<span style="position:absolute; top:4px; right:4px; width:7px; height:7px; background:var(--color-destructive); border-radius:50%;"></span>
</button>
<!-- Кнопка добавить -->
<button class="btn btn-primary"
hx-get="/modal-add-employee/"
hx-target="#modal-container"
hx-trigger="click">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="12" x2="12" y1="5" y2="19"/><line x1="5" x2="19" y1="12" y2="12"/>
</svg>
Додати
</button>
</div>
</header>
<!-- Контент (HTMX заменяет содержимое) -->
<main id="main-content" style="flex:1; overflow-y:auto; background:var(--color-background); padding:1.5rem;">
{% if page_template %}
{% include page_template %}
{% else %}
<div id="page-loader" style="padding:2rem;color:var(--color-muted-foreground);font-size:0.875rem;">Завантаження...</div>
{% endif %}
</main>
</div>
<!-- Контейнер модалок -->
<div id="modal-container"></div>
</div>
<script>
// Устанавливаем активный пункт меню на основе текущего URL
function setActiveNavItem() {
const currentPath = window.location.pathname;
document.querySelectorAll('.nav-item').forEach(item => {
const hxGet = item.getAttribute('hx-get');
if (hxGet && hxGet === currentPath) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
}
// Загружаем нужную страницу при старте
document.addEventListener('DOMContentLoaded', function() {
const mainContent = document.getElementById('main-content');
const isPlaceholder = mainContent.textContent.trim() === 'Завантаження...';
// Если контент пустой или это плейсхолдер, загружаем нужную страницу на основе URL
if (isPlaceholder) {
const currentPath = window.location.pathname;
const pageUrl = currentPath === '/' ? '/dashboard/' : currentPath;
htmx.ajax('GET', pageUrl, {target:'#main-content', swap:'innerHTML'});
}
// Всегда устанавливаем активный пункт меню после загрузки DOM
setActiveNavItem();
});
// После загрузки фрагмента — вызываем init нужной страницы
document.body.addEventListener('htmx:afterSwap', function(e) {
if (e.detail.target.id !== 'main-content') return;
// Обновляем активный пункт меню
setActiveNavItem();
// Инициализация страниц
if (typeof pharmaInit === 'function') pharmaInit();
if (typeof empInit === 'function') empInit();
if (typeof ttInit === 'function') ttInit();
});
</script>
</body>
</html>