Files
2026-06-15 21:04:54 +03:00

406 lines
26 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; }
.admin-toggle {
position: fixed; bottom: 20px; right: 20px; background: #1e3a5f; color: white;
border: none; border-radius: 50px; padding: 12px 20px; cursor: pointer;
font-weight: 600; z-index: 1000; box-shadow: 0 4px 15px rgba(0,0,0,0.2);
display: flex; align-items: center; gap: 8px;
}
.admin-toggle:hover { background: #0f2b40; transform: scale(1.02); }
.tabs { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.75rem; margin-bottom: 2rem; border-bottom: 2px solid #e2e8f0; padding-bottom: 0.75rem; }
.tab-btn { background: transparent; border: none; font-size: 0.95rem; font-weight: 600; padding: 0.6rem 1.5rem; border-radius: 40px; cursor: pointer; color: #475569; transition: all 0.2s; }
.tab-btn i { margin-right: 8px; }
.tab-btn.active { background: #1e3a5f; color: white; }
.tab-panel { display: none; animation: fadeIn 0.25s ease; }
.tab-panel.active-panel { display: block; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(5px);} to { opacity: 1; transform: translateY(0);} }
.org-grid { display: flex; flex-direction: column; gap: 1.5rem; }
.org-card { background: white; border-radius: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); border: 1px solid #eef2f6; overflow: hidden; transition: all 0.2s; }
.org-card:hover { box-shadow: 0 8px 25px rgba(0,0,0,0.08); }
.org-header { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; padding: 1.2rem 1.5rem; background: #ffffff; border-bottom: 1px solid #f0f2f5; cursor: pointer; }
.org-title { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.org-name { font-weight: 700; font-size: 1.2rem; color: #0f172a; }
.priority-badge { background: #eef2ff; color: #1e40af; border-radius: 40px; padding: 0.25rem 0.8rem; font-size: 0.7rem; font-weight: 600; }
.status-badge { border-radius: 40px; padding: 0.25rem 0.8rem; font-size: 0.7rem; font-weight: 600; }
.status-active { background: #dcfce7; color: #15803d; }
.status-warning { background: #fff3e3; color: #b45309; }
.toggle-icon { font-size: 1.2rem; color: #64748b; transition: transform 0.2s; }
.org-details { padding: 1.5rem; background: #fefefe; border-top: 1px solid #f1f5f9; display: none; }
.org-details.open { display: block; }
/* Стили для комитетов */
.committees-section { margin-top: 1.5rem; }
.committees-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 1rem; margin-top: 1rem; }
.committee-card { background: #f8fafc; border-radius: 16px; border: 1px solid #e2e8f0; overflow: hidden; transition: all 0.2s; }
.committee-card:hover { border-color: #cbd5e1; background: #fefefe; }
.committee-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem; background: #f1f5f9; cursor: pointer; }
.committee-name { font-weight: 600; color: #1e3a5f; }
.committee-status { font-size: 0.7rem; padding: 2px 8px; border-radius: 20px; }
.status-active { background: #dcfce7; color: #15803d; }
.status-medium { background: #fef3c7; color: #b45309; }
.status-inactive { background: #fee2e2; color: #dc2626; }
.committee-details { padding: 1rem; display: none; border-top: 1px solid #e2e8f0; }
.committee-details.open { display: block; }
.leader-info { background: white; border-radius: 12px; padding: 0.75rem; margin-bottom: 0.75rem; border-left: 3px solid #1e3a5f; }
.leader-label { font-size: 0.7rem; text-transform: uppercase; color: #6c86a3; font-weight: 600; }
.leader-name { font-weight: 700; color: #0f172a; margin: 4px 0; }
.leader-contacts { font-size: 0.8rem; color: #475569; }
.leader-contacts a { color: #1e3a5f; text-decoration: none; }
.leader-contacts a:hover { text-decoration: underline; }
.committee-description { font-size: 0.85rem; color: #334155; line-height: 1.5; margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid #e2e8f0; }
.details-section { margin-bottom: 1.5rem; }
.section-title { font-weight: 700; font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.5px; color: #5b6e8c; margin-bottom: 0.8rem; border-left: 3px solid #1e3a5f; padding-left: 10px; }
.section-title i { margin-right: 8px; }
.info-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1rem; }
.info-card { background: #f8fafc; border-radius: 16px; padding: 0.8rem 1rem; transition: all 0.2s; }
.info-card .label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.3px; font-weight: 600; color: #6c86a3; margin-bottom: 0.3rem; }
.info-card .value { font-size: 0.9rem; font-weight: 500; color: #1e2a44; line-height: 1.4; word-break: break-word; }
.full-width { grid-column: 1 / -1; }
.tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 5px; }
.tag { background: #e2e8f0; padding: 3px 10px; border-radius: 20px; font-size: 0.75rem; color: #334155; }
.links-list { display: flex; flex-wrap: wrap; gap: 8px; }
.resource-link { display: inline-block; background: #eef2ff; color: #1e3a5f; padding: 4px 12px; border-radius: 20px; font-size: 0.75rem; text-decoration: none; }
.resource-link:hover { background: #1e3a5f; color: white; }
.summary-wrapper { overflow-x: auto; background: white; border-radius: 24px; }
.summary-table { width: 100%; border-collapse: collapse; }
.summary-table th { background: #f1f5f9; padding: 14px; text-align: left; font-weight: 600; font-size: 0.85rem; }
.summary-table td { padding: 14px; border-bottom: 1px solid #eef2f8; font-size: 0.85rem; vertical-align: middle; }
.summary-table tr:hover { background: #fafcff; }
.footer-note { margin-top: 2rem; text-align: center; font-size: 0.75rem; color: #6c86a3; }
.loading { text-align: center; padding: 3rem; color: #64748b; }
@media (max-width: 750px) {
.committees-grid { grid-template-columns: 1fr; }
.info-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1><i class="fas fa-chart-line"></i> Бизнес-объединения ДНР</h1>
<p>Аналитический обзор с детальной информацией по комитетам организаций</p>
</div>
<div class="tabs">
<button class="tab-btn active" data-tab="detailed"><i class="fas fa-chart-simple"></i> Детальный анализ</button>
<button class="tab-btn" data-tab="summary"><i class="fas fa-table-list"></i> Сводная таблица</button>
<div>
<a href="/committees.html" style="display: inline-flex; align-items: center; gap: 8px; background: #1e3a5f; color: white; padding: 8px 20px; border-radius: 40px; text-decoration: none; font-size: 0.9rem;">
<i class="fas fa-users"></i> Комитеты организаций
</a>
</div>
</div>
<div id="detailedPanel" class="tab-panel active-panel">
<div id="detailedContainer" class="org-grid"><div class="loading"><i class="fas fa-spinner fa-spin"></i> Загрузка...</div></div>
</div>
<div id="summaryPanel" class="tab-panel">
<div class="summary-wrapper">
<table class="summary-table">
<thead>
<tr><th>Название</th><th>Тип</th><th>Статус</th><th>Членство</th><th>Приоритет</th><th>Ресурсы</th></thead>
<tbody id="summaryTableBody"><td><td colspan="6" class="loading">Загрузка...</tr></tr></tbody>
</table>
</div>
</div>
<div class="footer-note">
<i class="fas fa-info-circle"></i> Данные хранятся в файле data.json. Комитеты организаций загружаются из папки committees/.
</div>
</div>
<script>
let currentData = null;
let committeesData = {};
async function loadDataFromServer() {
try {
const response = await fetch('/data.json');
if (!response.ok) throw new Error('Ошибка загрузки');
currentData = await response.json();
if (!currentData.organizations) currentData.organizations = [];
if (!currentData.additionalOrganizations) currentData.additionalOrganizations = [];
// Загружаем комитеты для организаций
await loadCommittees();
renderDetailed();
renderSummaryTable();
} catch (error) {
console.error(error);
document.getElementById('detailedContainer').innerHTML = '<div class="loading" style="color:red;">❌ Ошибка загрузки данных. Убедитесь, что файл data.json существует.</div>';
}
}
async function loadCommittees() {
// Загружаем комитеты для ОПОРЫ РОССИИ
try {
const response = await fetch('/committees/opora_committees.json');
if (response.ok) {
committeesData['opora'] = await response.json();
}
} catch (error) {
console.log('Файл комитетов ОПОРЫ РОССИИ не найден');
committeesData['opora'] = [];
}
// Здесь можно добавить загрузку комитетов для других организаций
// Например, для Деловой России, СПП ДНР и т.д.
}
function getCommitteesForOrg(orgName) {
if (orgName.includes('ОПОРА РОССИИ')) {
return committeesData['opora'] || [];
}
return [];
}
function renderLeaderInfo(leader, title) {
if (!leader || !leader.name) return '<div class="leader-info"><div class="leader-label">Информация отсутствует</div></div>';
const statusMap = {
'active': 'Активен',
'medium': 'Средняя активность',
'inactive': 'Не активен',
'candidate': 'Кандидат'
};
const statusText = statusMap[leader.status] || leader.status || '—';
return `
<div class="leader-info">
<div class="leader-label">${title}</div>
<div class="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 style="font-size:0.75rem; color:#475569; margin-top:4px;">
<span class="tag">${escapeHtml(leader.title || '—')}</span>
<span class="tag">Статус: ${statusText}</span>
</div>
${leader.additional_info ? `<div style="font-size:0.75rem; color:#6c86a3; margin-top:6px;">${escapeHtml(leader.additional_info)}</div>` : ''}
</div>
`;
}
function renderCommittees(orgName) {
const committees = getCommitteesForOrg(orgName);
if (!committees || committees.length === 0) return '';
return `
<div class="committees-section">
<div class="section-title"><i class="fas fa-users"></i> Комитеты организации</div>
<div class="committees-grid" id="committees-${orgName.replace(/[^a-zA-Z0-9]/g, '_')}">
${committees.map((committee, idx) => `
<div class="committee-card">
<div class="committee-header" onclick="toggleCommitteeDetails('${orgName.replace(/[^a-zA-Z0-9]/g, '_')}_${idx}')">
<span class="committee-name"><i class="fas fa-folder"></i> ${escapeHtml(committee.name)}</span>
<span class="committee-status ${committee.current_leader?.status === 'active' ? 'status-active' : (committee.current_leader?.status === 'medium' ? 'status-medium' : 'status-inactive')}">
${committee.current_leader?.status === 'active' ? 'Активен' : (committee.current_leader?.status === 'medium' ? 'Средняя активность' : 'Статус не определён')}
</span>
</div>
<div class="committee-details" id="committee-details-${orgName.replace(/[^a-zA-Z0-9]/g, '_')}_${idx}">
${renderLeaderInfo(committee.current_leader, '👤 Текущий руководитель')}
${renderLeaderInfo(committee.candidate_for_leader, '👥 Кандидат на должность руководителя')}
<div class="committee-description">
<strong>О комитете:</strong><br>
${escapeHtml(committee.description.substring(0, 300))}${committee.description.length > 300 ? '...' : ''}
</div>
</div>
</div>
`).join('')}
</div>
</div>
`;
}
function renderDetailed() {
const container = document.getElementById('detailedContainer');
if (!currentData) return;
container.innerHTML = '';
(currentData.organizations || []).forEach(org => {
const d = org.detailed || {};
const priorityText = org.priority === 'high' ? 'Высокий' : (org.priority === 'medium' ? 'Средний' : 'Низкий');
const statusClass = org.status === 'active' ? 'status-active' : 'status-warning';
const statusText = org.status === 'active' ? '✔ Действует' : '⚠️ Требует уточнения';
let allLinks = [];
if (org.resources?.links) allLinks = [...org.resources.links];
if (org.links) allLinks = [...allLinks, ...org.links];
if (org.link && org.link !== '—' && !allLinks.includes(org.link)) allLinks.push(org.link);
allLinks = allLinks.filter(l => l && l.trim() !== '');
const membersDisplay = org.membersCount || org.summaryMembership || d.membership?.count || '—';
const card = document.createElement('div');
card.className = 'org-card';
card.innerHTML = `
<div class="org-header" onclick="toggleDetails(${org.id})">
<div class="org-title">
<span class="org-name"><i class="fas fa-building"></i> ${escapeHtml(org.name)}</span>
<span class="priority-badge">⭐ Приоритет: ${priorityText}</span>
<span class="status-badge ${statusClass}">${statusText}</span>
</div>
<div class="toggle-icon"><i class="fas fa-chevron-down" id="icon-${org.id}"></i></div>
</div>
<div class="org-details" id="details-${org.id}">
<div class="details-section">
<div class="section-title"><i class="fas fa-info-circle"></i> Общая информация</div>
<div class="info-grid">
<div class="info-card"><div class="label">📌 Полное наименование</div><div class="value">${escapeHtml(d.general?.fullName || org.name)}</div></div>
<div class="info-card"><div class="label">📅 Дата создания</div><div class="value">${escapeHtml(d.general?.created || 'Не указана')}</div></div>
<div class="info-card"><div class="label">🏛️ Юридическая форма</div><div class="value">${escapeHtml(d.general?.legalForm || 'Не указана')}</div></div>
<div class="info-card"><div class="label">📍 Адрес</div><div class="value">${escapeHtml(d.general?.address || 'Не указан')}</div></div>
<div class="info-card"><div class="label">👥 Количество членов</div><div class="value">${escapeHtml(membersDisplay)}</div></div>
<div class="info-card"><div class="label">🔗 Ресурсы</div><div class="value"><div class="links-list">${allLinks.map(link => `<a href="${escapeHtml(link)}" target="_blank" class="resource-link">${escapeHtml(link.length > 40 ? link.substring(0, 40) + '...' : link)}</a>`).join('') || '—'}</div></div>
</div>
</div>
<div class="details-section">
<div class="section-title"><i class="fas fa-users"></i> Руководство и структура</div>
<div class="info-grid">
<div class="info-card"><div class="label">👤 Руководитель</div><div class="value">${escapeHtml(d.leadership?.head || '—')}</div></div>
<div class="info-card"><div class="label">👥 Ключевые лица</div><div class="value">${escapeHtml(d.leadership?.keyPeople || '—')}</div></div>
<div class="info-card"><div class="label">🏢 Отделения</div><div class="value">${escapeHtml(d.leadership?.branches || '—')}</div></div>
</div>
</div>
<div class="details-section">
<div class="section-title"><i class="fas fa-handshake"></i> Членство и состав</div>
<div class="info-grid">
<div class="info-card"><div class="label">📋 Критерии вступления</div><div class="value">${escapeHtml(d.membership?.criteria || '—')}</div></div>
<div class="info-card"><div class="label">💰 Членские взносы</div><div class="value">${escapeHtml(d.membership?.fees || '—')}</div></div>
<div class="info-card full-width"><div class="label">🏭 Отраслевой состав</div><div class="value"><div class="tags">${renderTags(d.membership?.sectors || org.sectors || '—')}</div></div></div>
</div>
</div>
<div class="details-section">
<div class="section-title"><i class="fas fa-chart-line"></i> Деятельность и влияние</div>
<div class="info-grid">
<div class="info-card full-width"><div class="label">🎯 Цели и задачи</div><div class="value">${escapeHtml(d.goals || '—')}</div></div>
<div class="info-card full-width"><div class="label">⚡ Основная деятельность</div><div class="value">${escapeHtml(d.activity || '—')}</div></div>
<div class="info-card"><div class="label">📧 Контакты</div><div class="value">${escapeHtml(d.resources?.contacts || '—')}</div></div>
</div>
</div>
<div class="details-section">
<div class="section-title"><i class="fas fa-chart-simple"></i> SWOT-анализ</div>
<div class="info-grid">
<div class="info-card"><div class="label">✅ Сильные стороны</div><div class="value">${escapeHtml(d.assessment?.strengths || '—')}</div></div>
<div class="info-card"><div class="label">⚠️ Слабые стороны</div><div class="value">${escapeHtml(d.assessment?.weaknesses || '—')}</div></div>
<div class="info-card full-width"><div class="label">🎯 Рекомендации</div><div class="value">${escapeHtml(d.assessment?.priority || '—')}</div></div>
</div>
</div>
${renderCommittees(org.name)}
</div>
`;
container.appendChild(card);
});
}
function renderTags(tagsStr) {
if (!tagsStr || tagsStr === '—') return '<span class="tag">—</span>';
const tags = tagsStr.split(',').map(t => t.trim());
return tags.map(tag => `<span class="tag">${escapeHtml(tag)}</span>`).join('');
}
function renderSummaryTable() {
const tbody = document.getElementById('summaryTableBody');
if (!currentData) return;
tbody.innerHTML = '';
(currentData.organizations || []).forEach(org => { appendSummaryRow(tbody, org, 'main'); });
(currentData.additionalOrganizations || []).forEach(org => { appendSummaryRow(tbody, org, 'additional'); });
}
function appendSummaryRow(tbody, org, type) {
const statusClass = org.status === 'active' ? 'status-active' : 'status-warning';
const statusText = org.status === 'active' ? 'Действует' : 'Требует уточнения';
const priorityText = org.priority === 'high' ? 'Высокий' : (org.priority === 'medium' ? 'Средний' : 'Низкий');
const displayType = type === 'main' ? (org.shortType || 'Основная') : (org.type || 'Дополнительная');
const members = org.membersCount || (type === 'main' ? (org.summaryMembership || '—') : (org.members || '—'));
let allLinks = [];
if (org.resources?.links) allLinks = [...org.resources.links];
if (org.links) allLinks = [...allLinks, ...org.links];
if (org.link && org.link !== '—') allLinks.push(org.link);
allLinks = allLinks.filter(l => l && l.trim() !== '');
let linksHtml = '—';
if (allLinks.length > 0) {
linksHtml = `<div class="links-list">${allLinks.map(link => `<a href="${escapeHtml(link)}" target="_blank" class="resource-link">${escapeHtml(link.length > 40 ? link.substring(0, 40) + '...' : link)}</a>`).join('')}</div>`;
}
const row = document.createElement('tr');
row.innerHTML = `
<td style="font-weight:500;">${escapeHtml(org.name)}</td>
<td>${escapeHtml(displayType)}</td>
<td><span class="status-badge ${statusClass}">${statusText}</span></td>
<td>${escapeHtml(members)}</td>
<td>${priorityText}</td>
<td class="links-cell">${linksHtml}</td>
`;
tbody.appendChild(row);
}
window.toggleDetails = function(id) {
const el = document.getElementById(`details-${id}`);
const icon = document.getElementById(`icon-${id}`);
if (el.classList.contains('open')) {
el.classList.remove('open');
icon.className = 'fas fa-chevron-down';
} else {
el.classList.add('open');
icon.className = 'fas fa-chevron-up';
}
};
window.toggleCommitteeDetails = function(id) {
const el = document.getElementById(`committee-details-${id}`);
if (el.classList.contains('open')) {
el.classList.remove('open');
} else {
el.classList.add('open');
}
};
function escapeHtml(str) { if (!str) return ''; return str.replace(/[&<>]/g, m => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[m])); }
const btns = document.querySelectorAll('.tab-btn');
const panels = { detailed: document.getElementById('detailedPanel'), summary: document.getElementById('summaryPanel') };
btns.forEach(btn => {
btn.addEventListener('click', () => {
btns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const tabId = btn.getAttribute('data-tab');
Object.values(panels).forEach(p => p.classList.remove('active-panel'));
if (tabId === 'detailed') panels.detailed.classList.add('active-panel');
else panels.summary.classList.add('active-panel');
});
});
loadDataFromServer();
</script>
</body>
</html>