Код:
<div class="department-browser">
<div class="search-controls">
<div class="search-container">
<input type="text" id="department-search" class="search-input" placeholder="Поиск отдела..." />
<button class="dept-search-btn" onclick="<%= Scripts %>.searchElement()">
<svg class="search-icon" viewBox="0 0 24 24" width="16" height="16">
<path d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 0 0 1.48-5.34c-.47-2.78-2.79-5-5.59-5.34a6.505 6.505 0 0 0-7.27 7.27c.34 2.8 2.56 5.12 5.34 5.59a6.5 6.5 0 0 0 5.34-1.48l.27.28v.79l4.25 4.25c.41.41 1.08.41 1.49 0 .41-.41.41-1.08 0-1.49L15.5 14zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
</svg>
Найти
</button>
<button class="dept-clear-btn" onclick="<%= Scripts %>.clearSearch()">
<svg class="clear-icon" viewBox="0 0 24 24" width="16" height="16">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
Очистить
</button>
<% if (!Context.data.structure_data && !Context.data.elements_any ) { %>
<button class="dept-get-btn" onclick="<%= Scripts %>.getElements()">
Получить данные
</button>
<% } %>
<% if (Context.data.structure_data && !Context.data.elements_any ) { %>
<button class="dept-refresh-btn" onclick="<%= Scripts %>.getElements()">
<svg class="refresh-icon" viewBox="0 0 24 24" width="16" height="16">
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
</svg>
Обновить
</button>
<% } %>
</div>
</div>
<% if (Context.data.show_loader) { %>
<div class="status-container">
<div class="loading-state">
<div class="loading-indicator">
<svg width="80" height="80" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" stroke="#f0f2f5" stroke-width="8" fill="none"/>
<path class="loading-path" d="M50 10 A40 40 0 0 1 90 50" stroke="#4e73df" stroke-width="8"
fill="none" stroke-linecap="round">
<animateTransform attributeName="transform" type="rotate" from="0 50 50" to="360 50 50"
dur="1.5s" repeatCount="indefinite"/>
</path>
<circle cx="50" cy="50" r="6" fill="#4e73df"/>
<circle cx="50" cy="10" r="3" fill="#4e73df">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.5s" repeatCount="indefinite"/>
</circle>
</svg>
</div>
<p class="loading-text">Загрузка данных...</p>
</div>
</div>
<% } else if (!Context.data.show_loader && Context.data.structure_data) {
const rootItems = Context.data.structure_data.filter(item => item.subgroup.length > 0);
const orphanItems = Context.data.structure_data.filter(item => item.subgroup.length == 0);
%>
<ul id="departments-list" class="tree-view">
<% for (const division of rootItems) { %>
<li class="division" id="division-<%= division.id %>">
<div class="division-content clickable-area" onclick="<%= Scripts %>.handleDivisionClick(event, '<%= division.id %>')">
<span class="division-name"><%= division.name %></span>
<% if (division.subgroup && division.subgroup.length > 0) { %>
<span class="toggle-btn">
<svg class="toggle-icon" viewBox="0 0 24 24" width="16" height="16">
<path d="M7 10l5 5 5-5z"/>
</svg>
</span>
<% } %>
</div>
<div class="subgroup-container"></div>
</li>
<% } %>
<% if (orphanItems.length > 0) { %>
<li class="division" id="orphan-items">
<div class="division-content clickable-area" onclick="<%= Scripts %>.toggleOrphanItems(event)">
<span class="division-name">Незакрепленные элементы</span>
<span class="toggle-btn">
<svg class="toggle-icon" viewBox="0 0 24 24" width="16" height="16">
<path d="M7 10l5 5 5-5z"/>
</svg>
</span>
</div>
<div class="orphan-container">
<div class="orphan-scroll-wrapper">
<ul class="orphan-items-list">
<% orphanItems.forEach(item => { %>
<li class="group-app" id="group-<%= item.id %>">
<div class="group-content clickable-area">
<span class="group-name"><%= item.name %></span>
</div>
</li>
<% }); %>
</ul>
</div>
</div>
</li>
<% } %>
</ul>
<% } %>
</div>
<style>
.toggle-btn {
margin-left: auto;
background: none;
border: none;
padding: 4px;
}
.toggle-btn:hover {
background-color: #e0e0e0;
}
.toggle-icon {
fill: #666;
transition: transform 0.2s ease;
transform-origin: center;
}
.toggle-btn.active .toggle-icon {
transform: rotate(90deg);
fill: #4a90e2;
}
.dept-search-btn,
.dept-clear-btn,
.dept-refresh-btn,
.dept-get-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.dept-search-btn {
background-color: #4a90e2;
color: white;
}
.dept-search-btn:hover {
background-color: #3a7bc8;
}
.dept-clear-btn {
background-color: #f5f5f5;
color: #333;
}
.dept-clear-btn:hover {
background-color: #e5e5e5;
}
.dept-refresh-btn {
background-color: #f5f5f5;
color: #333;
}
.dept-refresh-btn:hover {
background-color: #e5e5e5;
}
.dept-get-btn {
background-color: #f5f5f5;
color: #333;
}
.dept-get-btn:hover {
background-color: #e5e5e5;
}
.department-browser {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.search-controls {
margin-bottom: 20px;
}
.search-container {
display: flex;
gap: 8px;
align-items: center;
}
.search-input {
flex: 1;
padding: 10px 15px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.3s;
}
.search-input:focus {
outline: none;
border-color: #4a90e2;
box-shadow: 0 0 0 2px rgba(74,144,226,0.2);
}
.tree-view {
position: relative;
}
.division {
margin: 8px 0;
list-style: none;
border-left: 2px solid #e0e0e0;
padding-left: 15px;
}
.division-content {
display: flex;
align-items: center;
padding: 6px 0;
}
.division-name {
flex: 1;
font-weight: 500;
color: #333;
}
.subgroup-container {
margin-left: 15px;
overflow: hidden;
max-height: 0;
opacity: 0;
transform: translateY(-10px);
transition:
max-height 0.3s ease-out,
opacity 0.2s ease-out,
transform 0.3s ease-out;
}
.subgroup-container.expanded {
max-height: 100vh;
opacity: 1;
transform: translateY(0);
transition:
max-height 0.5s ease-in,
opacity 0.3s ease-in 0.1s,
transform 0.4s ease-in;
}
.subgroup-container.finished {
max-height: none;
}
.group-app {
margin: 6px 0;
padding-left: 15px;
border-left: 1px dashed #e0e0e0;
list-style: none;
}
.group-name {
color: #555;
}
.highlight_class {
background-color: rgba(255, 235, 59, 0.3);
font-weight: 600;
padding: 2px 4px;
border-radius: 3px;
box-shadow: 0 0 0 1px rgba(255, 235, 59, 0.5);
}
svg {
fill: currentColor;
}
.clickable-area {
cursor: pointer;
padding: 6px 8px;
border-radius: 4px;
transition: background-color 0.2s ease;
}
.clickable-area:hover {
background-color: #f5f5f5;
}
.division-content {
display: flex;
align-items: center;
padding: 0;
}
.orphan-container {
margin-left: 20px;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.orphan-container.expanded {
max-height: 300px;
}
.orphan-scroll-wrapper {
max-height: 280px;
overflow-y: auto;
padding-right: 5px;
}
.orphan-scroll-wrapper::-webkit-scrollbar {
width: 8px;
}
.orphan-scroll-wrapper::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.orphan-scroll-wrapper::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
.orphan-scroll-wrapper::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
.orphan-items-list {
list-style: none;
padding-left: 15px;
margin: 5px 0;
}
.orphan-items-list .group-app {
padding: 6px 0;
border-left: 1px dashed #e0e0e0;
}
</style>