Fix project selection and add project settings UI

This commit is contained in:
Mikhail Chusavitin
2026-02-13 12:51:53 +03:00
parent 857ec7a0e5
commit 4e1a46bd71
7 changed files with 191 additions and 22 deletions

View File

@@ -448,9 +448,13 @@ async function createConfig() {
let projectUUID = '';
if (projectName) {
const existingProject = projectsCache.find(p => p.is_active && p.name.toLowerCase() === projectName.toLowerCase());
if (existingProject) {
projectUUID = existingProject.uuid;
const matchedProject = projectsCache.find(p => p.name.toLowerCase() === projectName.toLowerCase());
if (matchedProject) {
if (!matchedProject.is_active) {
alert('Проект с таким названием находится в архиве. Восстановите его или выберите другой.');
return;
}
projectUUID = matchedProject.uuid;
} else {
pendingCreateConfigName = name;
pendingCreateProjectName = projectName;
@@ -529,9 +533,13 @@ async function confirmMoveProject() {
let projectUUID = '';
if (projectName) {
const existingProject = projectsCache.find(p => p.is_active && p.name.toLowerCase() === projectName.toLowerCase());
if (existingProject) {
projectUUID = existingProject.uuid;
const matchedProject = projectsCache.find(p => p.name.toLowerCase() === projectName.toLowerCase());
if (matchedProject) {
if (!matchedProject.is_active) {
alert('Проект с таким названием находится в архиве. Восстановите его или выберите другой.');
return;
}
projectUUID = matchedProject.uuid;
} else {
pendingMoveConfigUUID = uuid;
pendingMoveProjectName = projectName;
@@ -587,6 +595,10 @@ async function confirmCreateProjectOnMove() {
body: JSON.stringify({ name: projectName })
});
if (!createResp.ok) {
if (createResp.status === 409) {
alert('Проект с таким названием уже существует');
return;
}
const err = await createResp.json();
alert('Не удалось создать проект: ' + (err.error || 'ошибка'));
return;
@@ -623,6 +635,10 @@ async function confirmCreateProjectOnMove() {
body: JSON.stringify({ name: projectName })
});
if (!createResp.ok) {
if (createResp.status === 409) {
alert('Проект с таким названием уже существует');
return;
}
const err = await createResp.json();
alert('Не удалось создать проект: ' + (err.error || 'ошибка'));
return;

View File

@@ -13,13 +13,16 @@
</div>
</div>
<div id="action-buttons" class="mt-4 grid grid-cols-1 sm:grid-cols-2 gap-3">
<div id="action-buttons" class="mt-4 grid grid-cols-1 sm:grid-cols-3 gap-3">
<button onclick="openCreateModal()" class="py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium">
+ Создать новую квоту
</button>
<button onclick="openImportModal()" class="py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 font-medium">
Импорт квоты
</button>
<button onclick="openProjectSettingsModal()" class="py-3 bg-gray-700 text-white rounded-lg hover:bg-gray-800 font-medium">
Параметры
</button>
</div>
<div class="mt-2">
<a id="tracker-link" href="https://tracker.yandex.ru/OPS-1933" target="_blank" rel="noopener noreferrer" class="text-sm text-blue-600 hover:text-blue-800 hover:underline">
@@ -113,6 +116,29 @@
</div>
</div>
<div id="project-settings-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md mx-4 p-6">
<h2 class="text-xl font-semibold mb-4">Параметры проекта</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Название проекта</label>
<input type="text" id="project-settings-name"
class="w-full px-3 py-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Ссылка для "открыть в трекере"</label>
<input type="text" id="project-settings-tracker-url" placeholder="https://tracker.example.com/PROJ-123"
class="w-full px-3 py-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<div class="text-xs text-gray-500 mt-1">Оставьте пустым, чтобы скрыть ссылку.</div>
</div>
</div>
<div class="flex justify-end space-x-3 mt-6">
<button onclick="closeProjectSettingsModal()" class="px-4 py-2 text-gray-600 hover:text-gray-800">Отмена</button>
<button onclick="saveProjectSettings()" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Сохранить</button>
</div>
</div>
</div>
<script>
const projectUUID = '{{.ProjectUUID}}';
let configStatusMode = 'active';
@@ -397,6 +423,59 @@ function closeImportModal() {
document.getElementById('import-modal').classList.remove('flex');
}
function openProjectSettingsModal() {
if (!project) return;
if (project.is_system) {
alert('Системный проект нельзя редактировать');
return;
}
document.getElementById('project-settings-name').value = project.name || '';
document.getElementById('project-settings-tracker-url').value = (project.tracker_url || '').trim();
document.getElementById('project-settings-modal').classList.remove('hidden');
document.getElementById('project-settings-modal').classList.add('flex');
}
function closeProjectSettingsModal() {
document.getElementById('project-settings-modal').classList.add('hidden');
document.getElementById('project-settings-modal').classList.remove('flex');
}
async function saveProjectSettings() {
if (!project) return;
const name = document.getElementById('project-settings-name').value.trim();
const trackerURL = document.getElementById('project-settings-tracker-url').value.trim();
if (!name) {
alert('Введите название проекта');
return;
}
const resp = await fetch('/api/projects/' + projectUUID, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: name, tracker_url: trackerURL})
});
if (!resp.ok) {
if (resp.status === 409) {
alert('Проект с таким названием уже существует');
return;
}
alert('Не удалось сохранить параметры проекта');
return;
}
project = await resp.json();
document.getElementById('project-title').textContent = project.name;
const trackerLink = document.getElementById('tracker-link');
if (trackerLink) {
const trackerURLResolved = resolveProjectTrackerURL(project);
if (trackerURLResolved) {
trackerLink.href = trackerURLResolved;
trackerLink.classList.remove('hidden');
} else {
trackerLink.classList.add('hidden');
}
}
closeProjectSettingsModal();
}
async function loadImportOptions() {
const resp = await fetch('/api/configs?page=1&per_page=500&status=active');
if (!resp.ok) return;
@@ -480,12 +559,14 @@ document.getElementById('create-modal').addEventListener('click', function(e) {
document.getElementById('rename-modal').addEventListener('click', function(e) { if (e.target === this) closeRenameModal(); });
document.getElementById('clone-modal').addEventListener('click', function(e) { if (e.target === this) closeCloneModal(); });
document.getElementById('import-modal').addEventListener('click', function(e) { if (e.target === this) closeImportModal(); });
document.getElementById('project-settings-modal').addEventListener('click', function(e) { if (e.target === this) closeProjectSettingsModal(); });
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeCreateModal();
closeRenameModal();
closeCloneModal();
closeImportModal();
closeProjectSettingsModal();
}
});

View File

@@ -294,6 +294,10 @@ async function createProject() {
})
});
if (!resp.ok) {
if (resp.status === 409) {
alert('Проект с таким названием уже существует');
return;
}
alert('Не удалось создать проект');
return;
}
@@ -310,6 +314,10 @@ async function renameProject(projectUUID, currentName) {
body: JSON.stringify({name: name.trim()})
});
if (!resp.ok) {
if (resp.status === 409) {
alert('Проект с таким названием уже существует');
return;
}
alert('Не удалось переименовать проект');
return;
}
@@ -360,6 +368,10 @@ async function copyProject(projectUUID, projectName) {
body: JSON.stringify({name: newName.trim()})
});
if (!createResp.ok) {
if (createResp.status === 409) {
alert('Проект с таким названием уже существует');
return;
}
alert('Не удалось создать копию проекта');
return;
}