302 lines
12 KiB
JavaScript
302 lines
12 KiB
JavaScript
const API_BASE_URL = window.location.origin;
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
// Инициализация табов
|
||
const tabButtons = document.querySelectorAll('.tab-button');
|
||
console.log('Найдено табов:', tabButtons.length);
|
||
|
||
tabButtons.forEach(button => {
|
||
button.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
const tabName = button.getAttribute('data-tab');
|
||
console.log('Клик на таб:', tabName);
|
||
switchTab(tabName);
|
||
});
|
||
});
|
||
|
||
// Traditional summary form
|
||
const form = document.getElementById('summaryForm');
|
||
const periodSelect = document.getElementById('period');
|
||
const customDatesDiv = document.getElementById('customDates');
|
||
const loadingDiv = document.getElementById('loading');
|
||
const errorDiv = document.getElementById('error');
|
||
const resultsDiv = document.getElementById('results');
|
||
|
||
// AI summary form
|
||
const aiForm = document.getElementById('aiSummaryForm');
|
||
const aiPeriodSelect = document.getElementById('aiPeriod');
|
||
const aiCustomDatesDiv = document.getElementById('aiCustomDates');
|
||
|
||
// Показываем/скрываем поля для произвольного периода (Traditional)
|
||
periodSelect.addEventListener('change', (e) => {
|
||
if (e.target.value === 'custom') {
|
||
customDatesDiv.style.display = 'grid';
|
||
} else {
|
||
customDatesDiv.style.display = 'none';
|
||
}
|
||
});
|
||
|
||
// Показываем/скрываем поля для произвольного периода (AI)
|
||
aiPeriodSelect.addEventListener('change', (e) => {
|
||
if (e.target.value === 'custom') {
|
||
aiCustomDatesDiv.style.display = 'grid';
|
||
} else {
|
||
aiCustomDatesDiv.style.display = 'none';
|
||
}
|
||
});
|
||
|
||
// Traditional summary submit
|
||
form.addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
|
||
errorDiv.style.display = 'none';
|
||
resultsDiv.style.display = 'none';
|
||
loadingDiv.style.display = 'block';
|
||
|
||
try {
|
||
const repositoryPath = document.getElementById('repositoryPath').value || '.';
|
||
const period = document.getElementById('period').value;
|
||
const startDate = document.getElementById('startDate').value;
|
||
const endDate = document.getElementById('endDate').value;
|
||
|
||
const requestBody = {
|
||
repositoryPath: repositoryPath || undefined
|
||
};
|
||
|
||
if (period === 'custom') {
|
||
if (startDate) requestBody.startDate = startDate;
|
||
if (endDate) requestBody.endDate = endDate;
|
||
} else if (period) {
|
||
requestBody.period = period;
|
||
}
|
||
|
||
const response = await fetch(`${API_BASE_URL}/api/summary`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.json();
|
||
throw new Error(errorData.error || `Ошибка: ${response.statusText}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
displayResults(data, false);
|
||
|
||
} catch (error) {
|
||
showError(error.message);
|
||
} finally {
|
||
loadingDiv.style.display = 'none';
|
||
}
|
||
});
|
||
|
||
// AI summary submit
|
||
aiForm.addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
|
||
errorDiv.style.display = 'none';
|
||
resultsDiv.style.display = 'none';
|
||
loadingDiv.style.display = 'block';
|
||
|
||
try {
|
||
const repositoryPath = document.getElementById('aiRepositoryPath').value || '.';
|
||
const period = document.getElementById('aiPeriod').value;
|
||
const startDate = document.getElementById('aiStartDate').value;
|
||
const endDate = document.getElementById('aiEndDate').value;
|
||
|
||
const requestBody = {
|
||
repositoryPath: repositoryPath || undefined
|
||
};
|
||
|
||
if (period === 'custom') {
|
||
if (startDate) requestBody.startDate = startDate;
|
||
if (endDate) requestBody.endDate = endDate;
|
||
} else if (period) {
|
||
requestBody.period = period;
|
||
}
|
||
|
||
const response = await fetch(`${API_BASE_URL}/api/summary/ai`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.json();
|
||
throw new Error(errorData.error || `Ошибка: ${response.statusText}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
displayResults(data, true);
|
||
|
||
} catch (error) {
|
||
showError(error.message);
|
||
} finally {
|
||
loadingDiv.style.display = 'none';
|
||
}
|
||
});
|
||
});
|
||
|
||
function switchTab(tabName) {
|
||
console.log('switchTab вызвана с:', tabName);
|
||
|
||
// Скрыть все tab-content
|
||
const tabContents = document.querySelectorAll('.tab-content');
|
||
console.log('Найдено контентов:', tabContents.length);
|
||
tabContents.forEach(content => {
|
||
content.classList.remove('active');
|
||
console.log('Скрыт:', content.getAttribute('data-tab'));
|
||
});
|
||
|
||
// Убрать active класс со всех кнопок
|
||
const tabButtons = document.querySelectorAll('.tab-button');
|
||
console.log('Найдено кнопок:', tabButtons.length);
|
||
tabButtons.forEach(button => button.classList.remove('active'));
|
||
|
||
// Показать выбранный tab
|
||
const selectedContent = document.querySelector(`.tab-content[data-tab="${tabName}"]`);
|
||
console.log('Выбранный контент:', selectedContent);
|
||
if (selectedContent) {
|
||
selectedContent.classList.add('active');
|
||
console.log('Активирован контент для:', tabName);
|
||
}
|
||
|
||
// Выделить выбранную кнопку
|
||
const selectedButton = document.querySelector(`.tab-button[data-tab="${tabName}"]`);
|
||
console.log('Выбранная кнопка:', selectedButton);
|
||
if (selectedButton) {
|
||
selectedButton.classList.add('active');
|
||
console.log('Активирована кнопка для:', tabName);
|
||
}
|
||
}
|
||
|
||
function showError(message) {
|
||
const errorDiv = document.getElementById('error');
|
||
errorDiv.textContent = `Ошибка: ${message}`;
|
||
errorDiv.style.display = 'block';
|
||
}
|
||
|
||
function displayResults(data, isAiSummary) {
|
||
const resultsDiv = document.getElementById('results');
|
||
const aiSummaryResult = document.getElementById('aiSummaryResult');
|
||
resultsDiv.style.display = 'block';
|
||
|
||
// Если это AI саммари, показываем его
|
||
if (isAiSummary) {
|
||
aiSummaryResult.style.display = 'block';
|
||
document.getElementById('aiSummaryText').innerHTML = escapeHtml(data.summary || '');
|
||
document.getElementById('aiCommitsCount').textContent = data.commitsCount || 0;
|
||
} else {
|
||
aiSummaryResult.style.display = 'none';
|
||
}
|
||
|
||
// Период
|
||
const periodInfo = document.getElementById('periodInfo');
|
||
periodInfo.textContent = data.periodInfo?.description || data.period?.description || 'Период не указан';
|
||
|
||
// Статистика (для traditional summary)
|
||
if (!isAiSummary && data.stats) {
|
||
const stats = data.stats;
|
||
document.getElementById('totalCommits').textContent = formatNumber(stats.totalCommits || 0);
|
||
document.getElementById('totalFiles').textContent = formatNumber(stats.totalFilesChanged || 0);
|
||
document.getElementById('totalAdditions').textContent = formatNumber(stats.totalAdditions || 0);
|
||
document.getElementById('totalDeletions').textContent = formatNumber(stats.totalDeletions || 0);
|
||
document.getElementById('uniqueContributors').textContent = formatNumber(stats.uniqueContributors || 0);
|
||
|
||
// Категории
|
||
const categoriesDiv = document.getElementById('categories');
|
||
categoriesDiv.innerHTML = '';
|
||
|
||
if (data.categories && data.categories.length > 0) {
|
||
data.categories.forEach(category => {
|
||
const categoryCard = document.createElement('div');
|
||
categoryCard.className = 'category-card';
|
||
|
||
categoryCard.innerHTML = `
|
||
<div class="category-header">
|
||
<div class="category-name">${escapeHtml(category.category)}</div>
|
||
<div class="category-count">${category.commitsCount} коммитов</div>
|
||
</div>
|
||
<div class="category-description">${escapeHtml(category.description)}</div>
|
||
${category.keyChanges && category.keyChanges.length > 0 ? `
|
||
<div class="key-changes">
|
||
<div class="key-changes-title">Ключевые изменения:</div>
|
||
${category.keyChanges.map(change => `
|
||
<div class="key-change-item">${escapeHtml(change)}</div>
|
||
`).join('')}
|
||
</div>
|
||
` : ''}
|
||
`;
|
||
|
||
categoriesDiv.appendChild(categoryCard);
|
||
});
|
||
} else {
|
||
categoriesDiv.innerHTML = '<p>Категории не найдены</p>';
|
||
}
|
||
|
||
// Топ коммиты
|
||
const topCommitsDiv = document.getElementById('topCommits');
|
||
topCommitsDiv.innerHTML = '';
|
||
|
||
if (data.topCommits && data.topCommits.length > 0) {
|
||
data.topCommits.forEach(commit => {
|
||
const commitCard = document.createElement('div');
|
||
commitCard.className = 'commit-item';
|
||
|
||
const commitDate = new Date(commit.when).toLocaleDateString('ru-RU', {
|
||
year: 'numeric',
|
||
month: 'long',
|
||
day: 'numeric',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
|
||
commitCard.innerHTML = `
|
||
<div class="commit-header">
|
||
<div class="commit-message">${escapeHtml(commit.message)}</div>
|
||
<div class="commit-sha">${commit.sha.substring(0, 7)}</div>
|
||
</div>
|
||
<div class="commit-meta">
|
||
<span class="commit-author">👤 ${escapeHtml(commit.committer || commit.author)}</span>
|
||
${commit.author !== commit.committer
|
||
? `<span class="commit-author-info">(Автор: ${escapeHtml(commit.author)})</span>`
|
||
: ''
|
||
}
|
||
<span class="commit-date">📅 ${commitDate}</span>
|
||
</div>
|
||
<div class="commit-stats">
|
||
<span class="stat-badge files">📄 ${commit.filesChanged} файлов</span>
|
||
<span class="stat-badge additions">➕ ${commit.additions} добавлено</span>
|
||
<span class="stat-badge deletions">➖ ${commit.deletions} удалено</span>
|
||
</div>
|
||
`;
|
||
|
||
topCommitsDiv.appendChild(commitCard);
|
||
});
|
||
} else {
|
||
topCommitsDiv.innerHTML = '<p>Коммиты не найдены</p>';
|
||
}
|
||
} else if (isAiSummary) {
|
||
// Для AI саммари скрываем статистику
|
||
document.querySelector('.stats-grid').style.display = 'none';
|
||
document.querySelector('.categories-section').style.display = 'none';
|
||
document.querySelector('.top-commits-section').style.display = 'none';
|
||
resultsDiv.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
}
|
||
|
||
function formatNumber(num) {
|
||
return new Intl.NumberFormat('ru-RU').format(num);
|
||
}
|
||
|
||
function escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
} |