Lecture2_DSW / app.js
alexdatamed's picture
Upload app.js
fd8c9d1 verified
// Data Literacy Dashboard Application
class DataLiteracyApp {
constructor() {
this.currentPage = 'home';
this.currentProfession = '';
this.testCurrentQuestion = 0;
this.testAnswers = [];
// Application data
this.data = {
statistics: {
dataImportant: "87% співробітників вважають базові навички роботи з даними важливими",
dataTrained: "Тільки 40% співробітників почуваються підготовленими",
dataLiteracy: "64% керівників повідомляють про нестачу розуміння даних"
},
professions: {
marketing: {
title: "Маркетинг та продажі",
icon: "📊",
uses: [
"Аналіз поведінки клієнтів",
"Оптимізація рекламних кампаній",
"Персоналізація повідомлень",
"Прогнозування продажів",
"Аналіз ROI"
],
example: "DirectTV використовує USPS дані для персоналізації сторінок для людей, які переїжджають",
metrics: ["CTR", "Conversion Rate", "CAC", "LTV", "ROAS"],
interactive: "roi_calculator"
},
hr: {
title: "Людські ресурси",
icon: "👥",
uses: [
"Планування робочої сили",
"Оптимізація рекрутингу",
"Аналіз плинності кадрів",
"Оцінка ефективності навчання",
"Прогнозування продуктивності"
],
example: "Credit Suisse використовує HR аналітику для прогнозування ризику звільнення",
metrics: ["Turnover Rate", "Time to Hire", "Employee Satisfaction", "Training ROI"],
interactive: "turnover_simulator"
},
finance: {
title: "Фінанси",
icon: "💰",
uses: [
"Фінансові коефіцієнти",
"Аналіз прибутковості",
"Управління ризиками",
"Бюджетування та прогнозування",
"Контроль витрат"
],
example: "Банки використовують аналітику для категоризації клієнтів за кредитними ризиками",
metrics: ["ROI", "NPV", "Liquidity Ratio", "Debt-to-Equity", "Profit Margin"],
interactive: "financial_calculator"
},
operations: {
title: "Операції та логістика",
icon: "🚚",
uses: [
"Оптимізація ланцюга поставок",
"Прогнозне обслуговування",
"Управління запасами",
"Планування маршрутів",
"Контроль якості"
],
example: "Walmart використовує аналітику для оптимізації запасів та зменшення витрат на 20%",
metrics: ["Inventory Turnover", "Order Fulfillment Rate", "Lead Time", "Quality Score"],
interactive: "inventory_simulator"
},
customer_service: {
title: "Клієнтський сервіс",
icon: "📞",
uses: [
"Аналіз задоволеності клієнтів",
"Оптимізація часу відповіді",
"Прогнозування навантаження",
"Персоналізація підтримки",
"Аналіз відгуків"
],
example: "Компанії використовують аналітику для моніторингу в реальному часі та покращення досвіду клієнтів",
metrics: ["CSAT", "NPS", "First Call Resolution", "Average Handle Time"],
interactive: "service_dashboard"
},
manufacturing: {
title: "Виробництво",
icon: "🏭",
uses: [
"Прогнозне обслуговування обладнання",
"Контроль якості",
"Оптимізація виробничих процесів",
"Управління ресурсами",
"Планування виробництва"
],
example: "United Airlines використовує прогнозну аналітику для технічного обслуговування літаків",
metrics: ["OEE", "Downtime", "Quality Rate", "Production Cost", "Throughput"],
interactive: "equipment_monitor"
},
healthcare: {
title: "Охорона здоров'я",
icon: "🏥",
uses: [
"Прогнозування захворюваності",
"Оптимізація розкладу персоналу",
"Управління запасами ліків",
"Аналіз ефективності лікування",
"Планування ресурсів"
],
example: "Лікарні використовують ML для прогнозування кількості пацієнтів у відділенні невідкладної допомоги",
metrics: ["Patient Satisfaction", "Readmission Rate", "Average Length of Stay", "Resource Utilization"],
interactive: "hospital_predictor"
},
it: {
title: "IT та технології",
icon: "💻",
uses: [
"Моніторинг системи",
"Аналіз продуктивності",
"Прогнозування відмов",
"Оптимізація ресурсів",
"Безпека даних"
],
example: "Компанії використовують аналітику для прогнозування та попередження системних збоїв",
metrics: ["Uptime", "Response Time", "Security Incidents", "Resource Utilization"],
interactive: "system_monitor"
}
},
literacyLevels: [
{
level: 1,
name: "Conversational",
description: "Розуміння базових термінів та концепцій",
skills: ["Читання графіків", "Розуміння основних метрик", "Участь в обговореннях даних"]
},
{
level: 2,
name: "Literacy",
description: "Здатність працювати з простими наборами даних",
skills: ["Фільтрація даних", "Створення базових звітів", "Інтерпретація результатів"]
},
{
level: 3,
name: "Competency",
description: "Впевнене використання аналітичних інструментів",
skills: ["Складні запити", "Візуалізація даних", "Статистичний аналіз"]
},
{
level: 4,
name: "Fluency",
description: "Експертний рівень аналітики",
skills: ["Машинне навчання", "Прогнозування", "Комплексне моделювання"]
},
{
level: 5,
name: "Multilingual",
description: "Лідерство в data-driven організаціях",
skills: ["Стратегія даних", "Управління командами", "Інновації в аналітиці"]
}
],
testQuestions: [
{
question: "Як часто ви використовуєте дані у своїй роботі?",
options: [
{ text: "Щодня", value: 4 },
{ text: "Кілька разів на тиждень", value: 3 },
{ text: "Кілька разів на місяць", value: 2 },
{ text: "Рідко або ніколи", value: 1 }
]
},
{
question: "Наскільки впевнено ви створюєте графіки та діаграми?",
options: [
{ text: "Дуже впевнено", value: 4 },
{ text: "Достатньо впевнено", value: 3 },
{ text: "Базово", value: 2 },
{ text: "Не вмію", value: 1 }
]
},
{
question: "Чи можете ви виявити тренди в даних?",
options: [
{ text: "Легко виявляю складні тренди", value: 4 },
{ text: "Виявляю основні тренди", value: 3 },
{ text: "Іноді помічаю очевидні тренди", value: 2 },
{ text: "Важко виявити тренди", value: 1 }
]
},
{
question: "Як ви ставитеся до статистичних понять?",
options: [
{ text: "Вільно використовую статистику", value: 4 },
{ text: "Розумію базові концепції", value: 3 },
{ text: "Знаю лише основи", value: 2 },
{ text: "Статистика складна для мене", value: 1 }
]
},
{
question: "Чи маєте ви досвід з аналітичними інструментами?",
options: [
{ text: "Використовую професійні інструменти", value: 4 },
{ text: "Працюю з Excel та подібними", value: 3 },
{ text: "Базове використання таблиць", value: 2 },
{ text: "Мінімальний досвід", value: 1 }
]
}
]
};
this.init();
}
init() {
this.renderProfessionNavigation();
this.renderLiteracyLevels();
this.bindEvents();
}
bindEvents() {
// Modal events
document.getElementById('interactiveModal').addEventListener('click', (e) => {
if (e.target.id === 'interactiveModal') {
this.closeModal();
}
});
document.getElementById('testModal').addEventListener('click', (e) => {
if (e.target.id === 'testModal') {
this.closeTestModal();
}
});
// Escape key to close modals
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.closeModal();
this.closeTestModal();
}
});
}
renderProfessionNavigation() {
const navContainer = document.getElementById('professionNav');
navContainer.innerHTML = '';
Object.entries(this.data.professions).forEach(([key, profession]) => {
const navItem = document.createElement('button');
navItem.className = 'nav-item';
navItem.onclick = () => this.showProfession(key);
navItem.innerHTML = `
<span class="nav-icon">${profession.icon}</span>
<span>${profession.title}</span>
`;
navContainer.appendChild(navItem);
});
}
renderLiteracyLevels() {
const container = document.getElementById('literacyLevels');
container.innerHTML = '';
this.data.literacyLevels.forEach(level => {
const levelElement = document.createElement('div');
levelElement.className = 'literacy-level';
levelElement.innerHTML = `
<div class="level-number">${level.level}</div>
<div class="level-info">
<div class="level-name">${level.name}</div>
<div class="level-description">${level.description}</div>
<div class="level-skills">
${level.skills.map(skill => `<span class="skill-tag">${skill}</span>`).join('')}
</div>
</div>
`;
container.appendChild(levelElement);
});
}
showHome() {
this.setActivePage('home');
this.setActiveNavItem(0);
}
showFirstProfession() {
const firstProfession = Object.keys(this.data.professions)[0];
this.showProfession(firstProfession);
}
showProfession(professionKey) {
this.currentProfession = professionKey;
const profession = this.data.professions[professionKey];
this.setActivePage('professionPages');
this.setActiveNavItem(Object.keys(this.data.professions).indexOf(professionKey) + 1);
const content = document.getElementById('professionContent');
content.innerHTML = `
<div class="profession-header">
<div class="profession-icon">${profession.icon}</div>
<div>
<h2 class="profession-title">${profession.title}</h2>
</div>
</div>
<div class="uses-section">
<h3>🎯 Способи використання даних</h3>
<div class="uses-list">
${profession.uses.map(use => `
<div class="use-item">${use}</div>
`).join('')}
</div>
</div>
<div class="example-section">
<h3>💡 Реальний приклад</h3>
<div class="example-card">
${profession.example}
</div>
</div>
<div class="metrics-section">
<h3>📊 Ключові метрики</h3>
<div class="metrics-grid">
${profession.metrics.map(metric => `
<div class="metric-tag">${metric}</div>
`).join('')}
</div>
</div>
<div class="interactive-section">
<h3>🎮 Інтерактивний інструмент</h3>
<p>Спробуйте практичний інструмент для цієї професії</p>
<button class="btn btn--primary" onclick="app.openInteractive('${profession.interactive}')">
Запустити інструмент
</button>
</div>
`;
}
showLiteracyLevels() {
this.setActivePage('literacyPage');
this.setActiveNavItem(Object.keys(this.data.professions).length + 1);
}
setActivePage(pageId) {
// Hide all pages
document.querySelectorAll('.page').forEach(page => {
page.classList.remove('active');
});
// Show selected page
document.getElementById(pageId).classList.add('active');
this.currentPage = pageId;
}
setActiveNavItem(index) {
// Remove active class from all nav items
document.querySelectorAll('.nav-item').forEach(item => {
item.classList.remove('active');
});
// Add active class to selected item
const navItems = document.querySelectorAll('.nav-item');
if (navItems[index]) {
navItems[index].classList.add('active');
}
}
openInteractive(type) {
const modal = document.getElementById('interactiveModal');
const modalTitle = document.getElementById('modalTitle');
const modalBody = document.getElementById('modalBody');
modalTitle.textContent = this.getInteractiveTitle(type);
modalBody.innerHTML = this.getInteractiveContent(type);
modal.classList.add('active');
this.bindInteractiveEvents(type);
}
getInteractiveTitle(type) {
const titles = {
'roi_calculator': 'Калькулятор ROI кампанії',
'turnover_simulator': 'Симулятор плинності кадрів',
'financial_calculator': 'Калькулятор фінансових коефіцієнтів',
'inventory_simulator': 'Симулятор управління запасами',
'service_dashboard': 'Дашборд метрик клієнтського сервісу',
'equipment_monitor': 'Монітор стану обладнання',
'hospital_predictor': 'Прогнозувач навантаження лікарні',
'system_monitor': 'Системний монітор'
};
return titles[type] || 'Інтерактивний інструмент';
}
getInteractiveContent(type) {
switch (type) {
case 'roi_calculator':
return `
<div class="calculator-grid">
<div class="form-group">
<label class="form-label">Витрати на кампанію (₴)</label>
<input type="number" class="form-control" id="campaignCost" value="10000">
</div>
<div class="form-group">
<label class="form-label">Дохід від кампанії (₴)</label>
<input type="number" class="form-control" id="campaignRevenue" value="25000">
</div>
</div>
<button class="btn btn--primary" onclick="app.calculateROI()">Розрахувати ROI</button>
<div id="roiResult" class="result-card" style="display: none;">
<div class="result-value" id="roiValue"></div>
<div>Рентабельність інвестицій</div>
</div>
`;
case 'turnover_simulator':
return `
<div class="simulator-controls">
<div class="slider-group">
<label class="form-label">Розмір зарплати: <span id="salaryValue">50000</span> ₴</label>
<input type="range" class="slider" id="salarySlider" min="30000" max="100000" value="50000" step="5000">
</div>
<div class="slider-group">
<label class="form-label">Задоволеність роботою: <span id="satisfactionValue">7</span>/10</label>
<input type="range" class="slider" id="satisfactionSlider" min="1" max="10" value="7">
</div>
<div class="slider-group">
<label class="form-label">Можливості розвитку: <span id="growthValue">6</span>/10</label>
<input type="range" class="slider" id="growthSlider" min="1" max="10" value="6">
</div>
</div>
<div id="turnoverResult" class="result-card">
<div class="result-value" id="turnoverRate">15%</div>
<div>Прогнозований рівень плинності</div>
</div>
`;
case 'financial_calculator':
return `
<div class="calculator-grid">
<div class="form-group">
<label class="form-label">Чистий прибуток (₴)</label>
<input type="number" class="form-control" id="netProfit" value="500000">
</div>
<div class="form-group">
<label class="form-label">Загальні активи (₴)</label>
<input type="number" class="form-control" id="totalAssets" value="2000000">
</div>
<div class="form-group">
<label class="form-label">Власний капітал (₴)</label>
<input type="number" class="form-control" id="equity" value="1200000">
</div>
<div class="form-group">
<label class="form-label">Дохід (₴)</label>
<input type="number" class="form-control" id="revenue" value="3000000">
</div>
</div>
<button class="btn btn--primary" onclick="app.calculateFinancials()">Розрахувати коефіцієнти</button>
<div id="financialResults" style="display: none; margin-top: 20px;">
<div class="metrics-grid">
<div class="metric-tag">ROA: <span id="roaValue"></span></div>
<div class="metric-tag">ROE: <span id="roeValue"></span></div>
<div class="metric-tag">Profit Margin: <span id="marginValue"></span></div>
</div>
</div>
`;
case 'inventory_simulator':
return `
<div class="simulator-controls">
<div class="slider-group">
<label class="form-label">Поточний запас: <span id="currentStock">100</span> одиниць</label>
<input type="range" class="slider" id="stockSlider" min="0" max="500" value="100" step="10">
</div>
<div class="slider-group">
<label class="form-label">Щоденне споживання: <span id="dailyDemand">15</span> одиниць</label>
<input type="range" class="slider" id="demandSlider" min="5" max="50" value="15">
</div>
<div class="slider-group">
<label class="form-label">Час поставки: <span id="leadTime">7</span> днів</label>
<input type="range" class="slider" id="leadSlider" min="1" max="30" value="7">
</div>
</div>
<div id="inventoryResult" class="result-card">
<div class="result-value" id="stockDays">7</div>
<div>Днів запасу залишилось</div>
</div>
`;
case 'service_dashboard':
return `
<div class="metrics-grid">
<div class="metric-tag">CSAT: <span id="csatMetric">4.2/5</span></div>
<div class="metric-tag">NPS: <span id="npsMetric">67</span></div>
<div class="metric-tag">FCR: <span id="fcrMetric">82%</span></div>
<div class="metric-tag">AHT: <span id="ahtMetric">3.5 хв</span></div>
</div>
<button class="btn btn--primary" onclick="app.refreshServiceMetrics()">Оновити метрики</button>
<div style="margin-top: 20px; padding: 15px; background: var(--color-secondary); border-radius: 8px;">
<h4>📊 Тренд задоволеності клієнтів</h4>
<p>📈 Зростання на 12% за останній місяць</p>
<p>⚡ Час відповіді покращився на 25%</p>
<p>🎯 85% звернень вирішено з першого разу</p>
</div>
`;
case 'equipment_monitor':
return `
<div class="metrics-grid">
<div class="metric-tag">Температура: <span id="tempMetric">72°C</span></div>
<div class="metric-tag">Вібрація: <span id="vibrMetric">Норма</span></div>
<div class="metric-tag">Ефективність: <span id="effMetric">94%</span></div>
<div class="metric-tag">Час роботи: <span id="uptimeMetric">99.2%</span></div>
</div>
<button class="btn btn--primary" onclick="app.refreshEquipmentData()">Оновити дані</button>
<div style="margin-top: 20px; padding: 15px; background: var(--color-success); color: var(--color-btn-primary-text); border-radius: 8px;">
<h4>✅ Прогнозна аналітика</h4>
<p>🔧 Наступне ТО через 15 днів</p>
<p>⚠️ Рекомендується заміна фільтра через 7 днів</p>
<p>📊 Ризик поломки: Низький (5%)</p>
</div>
`;
case 'hospital_predictor':
return `
<div class="simulator-controls">
<div class="slider-group">
<label class="form-label">День тижня: <span id="dayValue">Понеділок</span></label>
<input type="range" class="slider" id="daySlider" min="1" max="7" value="1">
</div>
<div class="slider-group">
<label class="form-label">Година: <span id="hourValue">12</span>:00</label>
<input type="range" class="slider" id="hourSlider" min="0" max="23" value="12">
</div>
<div class="slider-group">
<label class="form-label">Сезон: <span id="seasonValue">Зима</span></label>
<input type="range" class="slider" id="seasonSlider" min="1" max="4" value="1">
</div>
</div>
<div id="hospitalResult" class="result-card">
<div class="result-value" id="patientCount">23</div>
<div>Прогнозована кількість пацієнтів</div>
</div>
`;
case 'system_monitor':
return `
<div class="metrics-grid">
<div class="metric-tag">CPU: <span id="cpuMetric">45%</span></div>
<div class="metric-tag">RAM: <span id="ramMetric">67%</span></div>
<div class="metric-tag">Disk: <span id="diskMetric">23%</span></div>
<div class="metric-tag">Network: <span id="netMetric">12 Mbps</span></div>
</div>
<button class="btn btn--primary" onclick="app.refreshSystemData()">Оновити статус</button>
<div style="margin-top: 20px; padding: 15px; background: var(--color-success); color: var(--color-btn-primary-text); border-radius: 8px;">
<h4>🚨 Системні події</h4>
<p>✅ Всі сервіси працюють нормально</p>
<p>📊 Середній час відповіді: 120ms</p>
<p>🔒 Останній інцидент безпеки: 30 днів тому</p>
</div>
`;
default:
return '<p>Інтерактивний контент недоступний.</p>';
}
}
bindInteractiveEvents(type) {
switch (type) {
case 'turnover_simulator':
['salary', 'satisfaction', 'growth'].forEach(param => {
const slider = document.getElementById(`${param}Slider`);
const valueDisplay = document.getElementById(`${param}Value`);
slider.addEventListener('input', () => {
valueDisplay.textContent = slider.value;
this.updateTurnoverRate();
});
});
this.updateTurnoverRate();
break;
case 'inventory_simulator':
['stock', 'demand', 'lead'].forEach(param => {
const slider = document.getElementById(`${param}Slider`);
const valueDisplay = document.getElementById(`${param === 'stock' ? 'currentStock' : param === 'demand' ? 'dailyDemand' : 'leadTime'}Value`);
slider.addEventListener('input', () => {
valueDisplay.textContent = slider.value;
this.updateInventoryDays();
});
});
this.updateInventoryDays();
break;
case 'hospital_predictor':
const daySlider = document.getElementById('daySlider');
const hourSlider = document.getElementById('hourSlider');
const seasonSlider = document.getElementById('seasonSlider');
daySlider.addEventListener('input', () => {
const days = ['Понеділок', 'Вівторок', 'Середа', 'Четвер', 'П\'ятниця', 'Субота', 'Неділя'];
document.getElementById('dayValue').textContent = days[daySlider.value - 1];
this.updatePatientCount();
});
hourSlider.addEventListener('input', () => {
document.getElementById('hourValue').textContent = hourSlider.value;
this.updatePatientCount();
});
seasonSlider.addEventListener('input', () => {
const seasons = ['Зима', 'Весна', 'Літо', 'Осінь'];
document.getElementById('seasonValue').textContent = seasons[seasonSlider.value - 1];
this.updatePatientCount();
});
this.updatePatientCount();
break;
}
}
calculateROI() {
const cost = parseFloat(document.getElementById('campaignCost').value) || 0;
const revenue = parseFloat(document.getElementById('campaignRevenue').value) || 0;
const roi = cost > 0 ? ((revenue - cost) / cost * 100) : 0;
document.getElementById('roiResult').style.display = 'block';
document.getElementById('roiValue').textContent = `${roi.toFixed(1)}%`;
}
calculateFinancials() {
const profit = parseFloat(document.getElementById('netProfit').value) || 0;
const assets = parseFloat(document.getElementById('totalAssets').value) || 0;
const equity = parseFloat(document.getElementById('equity').value) || 0;
const revenue = parseFloat(document.getElementById('revenue').value) || 0;
const roa = assets > 0 ? (profit / assets * 100).toFixed(1) + '%' : '0%';
const roe = equity > 0 ? (profit / equity * 100).toFixed(1) + '%' : '0%';
const margin = revenue > 0 ? (profit / revenue * 100).toFixed(1) + '%' : '0%';
document.getElementById('financialResults').style.display = 'block';
document.getElementById('roaValue').textContent = roa;
document.getElementById('roeValue').textContent = roe;
document.getElementById('marginValue').textContent = margin;
}
updateTurnoverRate() {
const salary = parseInt(document.getElementById('salarySlider').value);
const satisfaction = parseInt(document.getElementById('satisfactionSlider').value);
const growth = parseInt(document.getElementById('growthSlider').value);
// Simple turnover calculation formula
const baseTurnover = 30;
const salaryFactor = (100000 - salary) / 100000 * 20;
const satisfactionFactor = (10 - satisfaction) * 3;
const growthFactor = (10 - growth) * 2;
const turnover = Math.max(0, baseTurnover + salaryFactor + satisfactionFactor + growthFactor);
document.getElementById('turnoverRate').textContent = `${turnover.toFixed(1)}%`;
}
updateInventoryDays() {
const stock = parseInt(document.getElementById('stockSlider').value);
const demand = parseInt(document.getElementById('demandSlider').value);
const days = demand > 0 ? Math.floor(stock / demand) : 0;
document.getElementById('stockDays').textContent = days;
}
updatePatientCount() {
const day = parseInt(document.getElementById('daySlider').value);
const hour = parseInt(document.getElementById('hourSlider').value);
const season = parseInt(document.getElementById('seasonSlider').value);
// Simple patient count prediction
let baseCount = 20;
// Day factor (weekends are busier)
if (day === 6 || day === 7) baseCount += 10;
// Hour factor (peak hours)
if (hour >= 9 && hour <= 17) baseCount += 15;
if (hour >= 18 && hour <= 22) baseCount += 8;
// Season factor (winter is busier)
if (season === 1) baseCount += 12; // Winter
if (season === 4) baseCount += 5; // Fall
document.getElementById('patientCount').textContent = baseCount;
}
refreshServiceMetrics() {
const metrics = {
csat: (3.8 + Math.random() * 1.2).toFixed(1) + '/5',
nps: Math.floor(50 + Math.random() * 40),
fcr: Math.floor(75 + Math.random() * 20) + '%',
aht: (2.5 + Math.random() * 2).toFixed(1) + ' хв'
};
document.getElementById('csatMetric').textContent = metrics.csat;
document.getElementById('npsMetric').textContent = metrics.nps;
document.getElementById('fcrMetric').textContent = metrics.fcr;
document.getElementById('ahtMetric').textContent = metrics.aht;
}
refreshEquipmentData() {
const metrics = {
temp: Math.floor(65 + Math.random() * 20) + '°C',
vibr: Math.random() > 0.2 ? 'Норма' : 'Увага',
eff: Math.floor(85 + Math.random() * 15) + '%',
uptime: (98 + Math.random() * 2).toFixed(1) + '%'
};
document.getElementById('tempMetric').textContent = metrics.temp;
document.getElementById('vibrMetric').textContent = metrics.vibr;
document.getElementById('effMetric').textContent = metrics.eff;
document.getElementById('uptimeMetric').textContent = metrics.uptime;
}
refreshSystemData() {
const metrics = {
cpu: Math.floor(20 + Math.random() * 60) + '%',
ram: Math.floor(40 + Math.random() * 40) + '%',
disk: Math.floor(15 + Math.random() * 30) + '%',
net: Math.floor(5 + Math.random() * 20) + ' Mbps'
};
document.getElementById('cpuMetric').textContent = metrics.cpu;
document.getElementById('ramMetric').textContent = metrics.ram;
document.getElementById('diskMetric').textContent = metrics.disk;
document.getElementById('netMetric').textContent = metrics.net;
}
startLiteracyTest() {
this.testCurrentQuestion = 0;
this.testAnswers = [];
this.showTestQuestion();
document.getElementById('testModal').classList.add('active');
}
showTestQuestion() {
const question = this.data.testQuestions[this.testCurrentQuestion];
const modalBody = document.getElementById('testModalBody');
modalBody.innerHTML = `
<div class="test-question">
<h4>Питання ${this.testCurrentQuestion + 1} з ${this.data.testQuestions.length}</h4>
<p>${question.question}</p>
<div class="test-options">
${question.options.map((option, index) => `
<div class="test-option" onclick="app.selectTestOption(${index}, ${option.value})">
${option.text}
</div>
`).join('')}
</div>
</div>
<div class="test-navigation">
<div class="test-progress">
Питання ${this.testCurrentQuestion + 1} з ${this.data.testQuestions.length}
</div>
<div>
${this.testCurrentQuestion > 0 ? '<button class="btn btn--secondary" onclick="app.previousQuestion()">Назад</button>' : ''}
<button class="btn btn--primary" id="nextBtn" onclick="app.nextQuestion()" disabled>Далі</button>
</div>
</div>
`;
}
selectTestOption(optionIndex, value) {
// Remove previous selection
document.querySelectorAll('.test-option').forEach(option => {
option.classList.remove('selected');
});
// Select current option
document.querySelectorAll('.test-option')[optionIndex].classList.add('selected');
// Store answer
this.testAnswers[this.testCurrentQuestion] = value;
// Enable next button
document.getElementById('nextBtn').disabled = false;
}
nextQuestion() {
if (this.testCurrentQuestion < this.data.testQuestions.length - 1) {
this.testCurrentQuestion++;
this.showTestQuestion();
} else {
this.showTestResults();
}
}
previousQuestion() {
if (this.testCurrentQuestion > 0) {
this.testCurrentQuestion--;
this.showTestQuestion();
}
}
showTestResults() {
const totalScore = this.testAnswers.reduce((sum, score) => sum + score, 0);
const averageScore = totalScore / this.data.testQuestions.length;
let level, recommendations;
if (averageScore >= 3.5) {
level = 4;
recommendations = "Ви маєте експертний рівень! Розвивайте лідерські навички в data-driven проектах.";
} else if (averageScore >= 2.5) {
level = 3;
recommendations = "Ваш рівень - впевнений користувач. Вивчайте статистичні методи та машинне навчання.";
} else if (averageScore >= 1.5) {
level = 2;
recommendations = "Базовий рівень. Рекомендуємо вивчити Excel та основи візуалізації даних.";
} else {
level = 1;
recommendations = "Початковий рівень. Почніть з основ: читання графіків та базових метрик.";
}
const modalBody = document.getElementById('testModalBody');
modalBody.innerHTML = `
<div style="text-align: center;">
<h3>🎯 Ваш рівень Data Literacy</h3>
<div class="result-card">
<div class="result-value">Рівень ${level}</div>
<div>${this.data.literacyLevels[level - 1].name}</div>
</div>
<p><strong>Оцінка:</strong> ${averageScore.toFixed(1)} з 4.0</p>
<p><strong>Рекомендації:</strong> ${recommendations}</p>
<button class="btn btn--primary" onclick="app.closeTestModal()">Закрити</button>
</div>
`;
}
closeModal() {
document.getElementById('interactiveModal').classList.remove('active');
}
closeTestModal() {
document.getElementById('testModal').classList.remove('active');
}
}
// Initialize the application when the page loads
let app;
document.addEventListener('DOMContentLoaded', () => {
app = new DataLiteracyApp();
});