Files
b_analysis/committees.html
2026-06-15 21:04:54 +03:00

481 lines
19 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>Комитеты бизнес-объединений ДНР</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Inter', sans-serif; background: #f8fafc; color: #0f172a; line-height: 1.5; padding: 2rem 1rem; }
.container { max-width: 1400px; margin: 0 auto; }
.header { text-align: center; margin-bottom: 2rem; }
.header h1 { font-size: 2rem; font-weight: 700; background: linear-gradient(135deg, #1e3a5f, #0f2b40); -webkit-background-clip: text; background-clip: text; color: transparent; }
.header p { color: #475569; margin-top: 0.5rem; }
.back-link {
display: inline-block;
margin-bottom: 1.5rem;
color: #1e3a5f;
text-decoration: none;
font-weight: 500;
}
.back-link:hover { text-decoration: underline; }
/* Фильтры */
.filters {
background: white;
border-radius: 20px;
padding: 1.5rem;
margin-bottom: 2rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
border: 1px solid #eef2f6;
}
.filter-group {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1rem;
}
.filter-group label {
font-weight: 600;
color: #475569;
}
.org-filter {
flex: 1;
min-width: 250px;
padding: 10px 14px;
border-radius: 12px;
border: 1px solid #cbd5e1;
font-family: 'Inter', sans-serif;
font-size: 0.9rem;
background: white;
}
.status-filter {
padding: 10px 14px;
border-radius: 12px;
border: 1px solid #cbd5e1;
font-family: 'Inter', sans-serif;
font-size: 0.9rem;
background: white;
}
.search-input {
flex: 2;
min-width: 250px;
padding: 10px 14px;
border-radius: 12px;
border: 1px solid #cbd5e1;
font-family: 'Inter', sans-serif;
font-size: 0.9rem;
}
.stats {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #e2e8f0;
font-size: 0.85rem;
color: #64748b;
}
/* Сетка комитетов */
.committees-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
gap: 1.5rem;
}
.committee-card {
background: white;
border-radius: 20px;
border: 1px solid #eef2f6;
overflow: hidden;
transition: all 0.2s;
box-shadow: 0 1px 3px rgba(0,0,0,0.03);
}
.committee-card:hover {
border-color: #cbd5e1;
box-shadow: 0 8px 20px rgba(0,0,0,0.05);
transform: translateY(-2px);
}
.card-header {
padding: 1.2rem;
background: linear-gradient(135deg, #1e3a5f, #0f2b40);
color: white;
}
.org-badge {
display: inline-block;
background: rgba(255,255,255,0.2);
padding: 4px 10px;
border-radius: 20px;
font-size: 0.7rem;
font-weight: 500;
margin-bottom: 0.75rem;
}
.committee-name {
font-size: 1.1rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.committee-status {
display: inline-block;
font-size: 0.7rem;
padding: 3px 10px;
border-radius: 20px;
background: rgba(255,255,255,0.15);
}
.card-body {
padding: 1.2rem;
}
.leader-section {
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid #eef2f6;
}
.leader-title {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #6c86a3;
font-weight: 600;
margin-bottom: 0.5rem;
}
.leader-name {
font-weight: 700;
color: #0f172a;
margin-bottom: 0.25rem;
}
.leader-contacts {
font-size: 0.75rem;
color: #475569;
}
.leader-contacts a {
color: #1e3a5f;
text-decoration: none;
}
.leader-contacts a:hover { text-decoration: underline; }
.leader-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 8px;
}
.tag {
background: #e2e8f0;
padding: 2px 8px;
border-radius: 16px;
font-size: 0.7rem;
color: #334155;
}
.committee-description {
font-size: 0.85rem;
color: #475569;
line-height: 1.5;
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid #eef2f6;
}
.expand-btn {
background: none;
border: none;
color: #1e3a5f;
cursor: pointer;
font-size: 0.8rem;
margin-top: 0.5rem;
font-weight: 500;
}
.full-description {
display: none;
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid #e2e8f0;
font-size: 0.85rem;
color: #334155;
line-height: 1.5;
}
.full-description.open { display: block; }
.no-results {
text-align: center;
padding: 3rem;
color: #64748b;
background: white;
border-radius: 20px;
}
.loading {
text-align: center;
padding: 3rem;
color: #64748b;
}
.footer-note {
margin-top: 2rem;
text-align: center;
font-size: 0.75rem;
color: #6c86a3;
border-top: 1px solid #e2edf7;
padding-top: 2rem;
}
@media (max-width: 700px) {
.committees-grid { grid-template-columns: 1fr; }
.filter-group { flex-direction: column; align-items: stretch; }
.org-filter, .status-filter, .search-input { width: 100%; }
}
</style>
</head>
<body>
<div class="container">
<a href="/" class="back-link"><i class="fas fa-arrow-left"></i> Назад к списку организаций</a>
<div class="header">
<h1><i class="fas fa-users"></i> Комитеты бизнес-объединений</h1>
<p>Детальная информация о комитетах, их руководителях и деятельности</p>
</div>
<div class="filters">
<div class="filter-group">
<label><i class="fas fa-building"></i> Организация:</label>
<select id="orgFilter" class="org-filter">
<option value="all">Все организации</option>
</select>
<label><i class="fas fa-flag-checkered"></i> Статус:</label>
<select id="statusFilter" class="status-filter">
<option value="all">Все статусы</option>
<option value="active">Активные</option>
<option value="medium">Средняя активность</option>
<option value="inactive">Неактивные</option>
<option value="candidate">С кандидатом</option>
</select>
<label><i class="fas fa-search"></i> Поиск:</label>
<input type="text" id="searchInput" class="search-input" placeholder="Название комитета, руководитель...">
</div>
<div class="stats" id="stats">
Загрузка...
</div>
</div>
<div id="committeesContainer" class="committees-grid">
<div class="loading"><i class="fas fa-spinner fa-spin"></i> Загрузка комитетов...</div>
</div>
<div class="footer-note">
<i class="fas fa-info-circle"></i> Данные о комитетах загружаются из папки committees/.
Статус активности определяется на основе информации о руководителе.
</div>
</div>
<script>
let allCommittees = [];
let organizationsList = [];
async function loadOrganizations() {
try {
const response = await fetch('/data.json');
if (response.ok) {
const data = await response.json();
organizationsList = data.organizations || [];
// Добавляем также дополнительные организации, если есть комитеты
const additional = data.additionalOrganizations || [];
organizationsList = [...organizationsList, ...additional];
}
} catch (error) {
console.error('Ошибка загрузки организаций:', error);
}
}
async function loadCommittees() {
const committeeFiles = [
{ file: '/committees/opora_committees.json', orgName: 'ОПОРА РОССИИ' },
// Добавьте другие файлы комитетов по мере появления:
// { file: '/committees/deloros_committees.json', orgName: 'Деловая Россия' },
// { file: '/committees/spp_committees.json', orgName: 'Союз промышленников и предпринимателей ДНР' },
];
allCommittees = [];
for (const source of committeeFiles) {
try {
const response = await fetch(source.file);
if (response.ok) {
const committees = await response.json();
committees.forEach(committee => {
allCommittees.push({
...committee,
organization: source.orgName,
organizationId: source.orgName.toLowerCase().replace(/[^a-z0-9]/g, '_')
});
});
}
} catch (error) {
console.log(`Файл ${source.file} не найден`);
}
}
renderFilters();
renderCommittees();
}
function renderFilters() {
const orgSelect = document.getElementById('orgFilter');
const uniqueOrgs = [...new Set(allCommittees.map(c => c.organization))];
orgSelect.innerHTML = '<option value="all">Все организации</option>';
uniqueOrgs.forEach(org => {
const option = document.createElement('option');
option.value = org;
option.textContent = org;
orgSelect.appendChild(option);
});
}
function getLeaderStatus(leader) {
if (!leader || !leader.status) return 'unknown';
return leader.status;
}
function filterCommittees() {
const orgFilter = document.getElementById('orgFilter').value;
const statusFilter = document.getElementById('statusFilter').value;
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
return allCommittees.filter(committee => {
// Фильтр по организации
if (orgFilter !== 'all' && committee.organization !== orgFilter) return false;
// Фильтр по статусу
if (statusFilter !== 'all') {
const leaderStatus = getLeaderStatus(committee.current_leader);
if (statusFilter === 'candidate') {
if (!committee.candidate_for_leader?.name) return false;
} else if (leaderStatus !== statusFilter) return false;
}
// Поиск
if (searchTerm) {
const searchFields = [
committee.name,
committee.current_leader?.name,
committee.current_leader?.title,
committee.candidate_for_leader?.name,
committee.description
].filter(Boolean).join(' ').toLowerCase();
if (!searchFields.includes(searchTerm)) return false;
}
return true;
});
}
function renderCommittees() {
const container = document.getElementById('committeesContainer');
const filtered = filterCommittees();
const stats = document.getElementById('stats');
stats.innerHTML = `Найдено: ${filtered.length} комитетов из ${allCommittees.length}`;
if (filtered.length === 0) {
container.innerHTML = '<div class="no-results"><i class="fas fa-inbox"></i><br>Комитеты не найдены. Попробуйте изменить параметры фильтрации.</div>';
return;
}
container.innerHTML = '';
filtered.forEach((committee, idx) => {
const leader = committee.current_leader;
const candidate = committee.candidate_for_leader;
const statusClass = leader?.status === 'active' ? 'Активен' : (leader?.status === 'medium' ? 'Средняя активность' : 'Статус не определён');
const card = document.createElement('div');
card.className = 'committee-card';
card.innerHTML = `
<div class="card-header">
<div class="org-badge"><i class="fas fa-building"></i> ${escapeHtml(committee.organization)}</div>
<div class="committee-name">${escapeHtml(committee.name)}</div>
<div class="committee-status">${statusClass}</div>
</div>
<div class="card-body">
<div class="leader-section">
<div class="leader-title"><i class="fas fa-user-tie"></i> Текущий руководитель</div>
<div class="leader-name">${leader?.name ? escapeHtml(leader.name) : '—'}</div>
<div class="leader-contacts">
${leader?.contacts?.email ? `<i class="fas fa-envelope"></i> <a href="mailto:${escapeHtml(leader.contacts.email)}">${escapeHtml(leader.contacts.email)}</a><br>` : ''}
${leader?.contacts?.phone ? `<i class="fas fa-phone"></i> <a href="tel:${escapeHtml(leader.contacts.phone)}">${escapeHtml(leader.contacts.phone)}</a><br>` : ''}
${leader?.contacts?.telegram ? `<i class="fab fa-telegram"></i> <a href="https://t.me/${escapeHtml(leader.contacts.telegram.replace('@', ''))}" target="_blank">${escapeHtml(leader.contacts.telegram)}</a><br>` : ''}
</div>
<div class="leader-tags">
${leader?.title ? `<span class="tag">${escapeHtml(leader.title)}</span>` : ''}
${leader?.additional_info ? `<span class="tag">${escapeHtml(leader.additional_info.substring(0, 50))}${leader.additional_info.length > 50 ? '...' : ''}</span>` : ''}
</div>
</div>
${candidate?.name ? `
<div class="leader-section">
<div class="leader-title"><i class="fas fa-user-plus"></i> Кандидат на должность</div>
<div class="leader-name">${escapeHtml(candidate.name)}</div>
<div class="leader-contacts">
${candidate.contacts?.email ? `<i class="fas fa-envelope"></i> <a href="mailto:${escapeHtml(candidate.contacts.email)}">${escapeHtml(candidate.contacts.email)}</a><br>` : ''}
${candidate.contacts?.phone ? `<i class="fas fa-phone"></i> <a href="tel:${escapeHtml(candidate.contacts.phone)}">${escapeHtml(candidate.contacts.phone)}</a><br>` : ''}
${candidate.contacts?.telegram ? `<i class="fab fa-telegram"></i> <a href="https://t.me/${escapeHtml(candidate.contacts.telegram.replace('@', ''))}" target="_blank">${escapeHtml(candidate.contacts.telegram)}</a><br>` : ''}
</div>
<div class="leader-tags">
${candidate.title ? `<span class="tag">${escapeHtml(candidate.title)}</span>` : ''}
${candidate.additional_info ? `<span class="tag">${escapeHtml(candidate.additional_info.substring(0, 50))}${candidate.additional_info.length > 50 ? '...' : ''}</span>` : ''}
</div>
</div>
` : ''}
<div class="committee-description">
<strong>О комитете:</strong><br>
${escapeHtml(committee.description.substring(0, 200))}${committee.description.length > 200 ? '...' : ''}
</div>
${committee.description.length > 200 ? `
<button class="expand-btn" onclick="toggleDescription(this)">
<i class="fas fa-chevron-down"></i> Читать полностью
</button>
<div class="full-description">
${escapeHtml(committee.description)}
</div>
` : ''}
</div>
`;
container.appendChild(card);
});
}
function toggleDescription(btn) {
const fullDesc = btn.nextElementSibling;
const icon = btn.querySelector('i');
if (fullDesc.classList.contains('open')) {
fullDesc.classList.remove('open');
icon.className = 'fas fa-chevron-down';
btn.innerHTML = '<i class="fas fa-chevron-down"></i> Читать полностью';
} else {
fullDesc.classList.add('open');
icon.className = 'fas fa-chevron-up';
btn.innerHTML = '<i class="fas fa-chevron-up"></i> Свернуть';
}
}
function escapeHtml(str) {
if (!str) return '';
return str.replace(/[&<>]/g, m => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[m]));
}
// Добавляем обработчики фильтров
document.getElementById('orgFilter').addEventListener('change', () => renderCommittees());
document.getElementById('statusFilter').addEventListener('change', () => renderCommittees());
document.getElementById('searchInput').addEventListener('input', () => renderCommittees());
// Загрузка данных
async function init() {
await loadOrganizations();
await loadCommittees();
}
init();
</script>
</body>
</html>