configs: save pending template changes

This commit is contained in:
Mikhail Chusavitin
2026-02-06 16:43:04 +03:00
parent 2f0ac2f6d2
commit 29035ddc5a

View File

@@ -63,10 +63,16 @@
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-700 mb-1">Проект</label> <label class="block text-sm font-medium text-gray-700 mb-1">Проект</label>
<select id="create-project-select" <input id="create-project-input"
class="w-full px-3 py-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> list="create-project-options"
<option value="">Без проекта</option> placeholder="Начните вводить название проекта"
</select> class="w-full px-3 py-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<datalist id="create-project-options"></datalist>
<div class="mt-2 flex justify-between items-center gap-3">
<button type="button" onclick="clearCreateProjectInput()" class="text-sm text-gray-600 hover:text-gray-800">
Без проекта
</button>
</div>
</div> </div>
</div> </div>
@@ -171,10 +177,10 @@
<div id="create-project-on-move-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50"> <div id="create-project-on-move-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"> <div class="bg-white rounded-lg shadow-xl w-full max-w-md mx-4 p-6">
<h2 class="text-xl font-semibold mb-3">Проект не найден</h2> <h2 class="text-xl font-semibold mb-3">Проект не найден</h2>
<p class="text-sm text-gray-600 mb-4">Проект "<span id="create-project-on-move-name" class="font-medium text-gray-900"></span>" не найден. Создать и привязать квоту?</p> <p class="text-sm text-gray-600 mb-4">Проект "<span id="create-project-on-move-name" class="font-medium text-gray-900"></span>" не найден. <span id="create-project-on-move-description">Создать и привязать квоту?</span></p>
<div class="flex justify-end space-x-3"> <div class="flex justify-end space-x-3">
<button onclick="closeCreateProjectOnMoveModal()" class="px-4 py-2 text-gray-600 hover:text-gray-800">Отмена</button> <button onclick="closeCreateProjectOnMoveModal()" class="px-4 py-2 text-gray-600 hover:text-gray-800">Отмена</button>
<button onclick="confirmCreateProjectOnMove()" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Создать и привязать</button> <button id="create-project-on-move-confirm-btn" onclick="confirmCreateProjectOnMove()" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Создать и привязать</button>
</div> </div>
</div> </div>
</div> </div>
@@ -190,6 +196,8 @@ let projectsCache = [];
let projectNameByUUID = {}; let projectNameByUUID = {};
let pendingMoveConfigUUID = ''; let pendingMoveConfigUUID = '';
let pendingMoveProjectName = ''; let pendingMoveProjectName = '';
let pendingCreateConfigName = '';
let pendingCreateProjectName = '';
function renderConfigs(configs) { function renderConfigs(configs) {
const emptyText = configStatusMode === 'archived' const emptyText = configStatusMode === 'archived'
@@ -407,6 +415,7 @@ async function cloneConfig() {
function openCreateModal() { function openCreateModal() {
document.getElementById('opportunity-number').value = ''; document.getElementById('opportunity-number').value = '';
document.getElementById('create-project-input').value = '';
document.getElementById('create-modal').classList.remove('hidden'); document.getElementById('create-modal').classList.remove('hidden');
document.getElementById('create-modal').classList.add('flex'); document.getElementById('create-modal').classList.add('flex');
document.getElementById('opportunity-number').focus(); document.getElementById('opportunity-number').focus();
@@ -425,8 +434,25 @@ async function createConfig() {
return; return;
} }
const projectUUID = document.getElementById('create-project-select').value; const projectName = document.getElementById('create-project-input').value.trim();
let projectUUID = '';
if (projectName) {
const existingProject = projectsCache.find(p => p.is_active && p.name.toLowerCase() === projectName.toLowerCase());
if (existingProject) {
projectUUID = existingProject.uuid;
} else {
pendingCreateConfigName = name;
pendingCreateProjectName = projectName;
openCreateProjectOnCreateModal(projectName);
return;
}
}
await createConfigWithProject(name, projectUUID);
}
async function createConfigWithProject(name, projectUUID) {
try { try {
const resp = await fetch('/api/configs', { const resp = await fetch('/api/configs', {
method: 'POST', method: 'POST',
@@ -442,16 +468,17 @@ async function createConfig() {
}) })
}); });
const config = await resp.json();
if (!resp.ok) { if (!resp.ok) {
const err = await resp.json(); alert('Ошибка: ' + (config.error || 'Не удалось создать'));
alert('Ошибка: ' + (err.error || 'Не удалось создать')); return false;
return;
} }
const config = await resp.json();
window.location.href = '/configurator?uuid=' + config.uuid; window.location.href = '/configurator?uuid=' + config.uuid;
return true;
} catch(e) { } catch(e) {
alert('Ошибка создания конфигурации'); alert('Ошибка создания конфигурации');
return false;
} }
} }
@@ -510,8 +537,22 @@ function clearMoveProjectInput() {
document.getElementById('move-project-input').value = ''; document.getElementById('move-project-input').value = '';
} }
function clearCreateProjectInput() {
document.getElementById('create-project-input').value = '';
}
function openCreateProjectOnMoveModal(projectName) { function openCreateProjectOnMoveModal(projectName) {
document.getElementById('create-project-on-move-name').textContent = projectName; document.getElementById('create-project-on-move-name').textContent = projectName;
document.getElementById('create-project-on-move-description').textContent = 'Создать и привязать квоту?';
document.getElementById('create-project-on-move-confirm-btn').textContent = 'Создать и привязать';
document.getElementById('create-project-on-move-modal').classList.remove('hidden');
document.getElementById('create-project-on-move-modal').classList.add('flex');
}
function openCreateProjectOnCreateModal(projectName) {
document.getElementById('create-project-on-move-name').textContent = projectName;
document.getElementById('create-project-on-move-description').textContent = 'Создать и использовать для новой конфигурации?';
document.getElementById('create-project-on-move-confirm-btn').textContent = 'Создать и использовать';
document.getElementById('create-project-on-move-modal').classList.remove('hidden'); document.getElementById('create-project-on-move-modal').classList.remove('hidden');
document.getElementById('create-project-on-move-modal').classList.add('flex'); document.getElementById('create-project-on-move-modal').classList.add('flex');
} }
@@ -521,9 +562,43 @@ function closeCreateProjectOnMoveModal() {
document.getElementById('create-project-on-move-modal').classList.remove('flex'); document.getElementById('create-project-on-move-modal').classList.remove('flex');
pendingMoveConfigUUID = ''; pendingMoveConfigUUID = '';
pendingMoveProjectName = ''; pendingMoveProjectName = '';
pendingCreateConfigName = '';
pendingCreateProjectName = '';
} }
async function confirmCreateProjectOnMove() { async function confirmCreateProjectOnMove() {
if (pendingCreateConfigName && pendingCreateProjectName) {
const configName = pendingCreateConfigName;
const projectName = pendingCreateProjectName;
try {
const createResp = await fetch('/api/projects', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ name: projectName })
});
if (!createResp.ok) {
const err = await createResp.json();
alert('Не удалось создать проект: ' + (err.error || 'ошибка'));
return;
}
const newProject = await createResp.json();
pendingCreateConfigName = '';
pendingCreateProjectName = '';
await loadProjectsForConfigUI();
const created = await createConfigWithProject(configName, newProject.uuid);
if (created) {
closeCreateProjectOnMoveModal();
} else {
closeCreateProjectOnMoveModal();
document.getElementById('create-project-input').value = projectName;
}
} catch (e) {
alert('Ошибка создания проекта');
}
return;
}
const configUUID = pendingMoveConfigUUID; const configUUID = pendingMoveConfigUUID;
const projectName = pendingMoveProjectName; const projectName = pendingMoveProjectName;
if (!configUUID || !projectName) { if (!configUUID || !projectName) {
@@ -544,10 +619,15 @@ async function confirmCreateProjectOnMove() {
} }
const newProject = await createResp.json(); const newProject = await createResp.json();
pendingMoveConfigUUID = '';
pendingMoveProjectName = '';
await loadProjectsForConfigUI();
document.getElementById('move-project-input').value = projectName;
const moved = await moveConfigToProject(configUUID, newProject.uuid); const moved = await moveConfigToProject(configUUID, newProject.uuid);
if (moved) { if (moved) {
closeCreateProjectOnMoveModal(); closeCreateProjectOnMoveModal();
closeMoveProjectModal(); } else {
closeCreateProjectOnMoveModal();
} }
} catch (e) { } catch (e) {
alert('Ошибка создания проекта'); alert('Ошибка создания проекта');
@@ -760,16 +840,18 @@ async function loadProjectsForConfigUI() {
const data = await resp.json(); const data = await resp.json();
projectsCache = (data.projects || []); projectsCache = (data.projects || []);
const select = document.getElementById('create-project-select'); projectsCache.forEach(project => {
if (select) { projectNameByUUID[project.uuid] = project.name;
select.innerHTML = '<option value="">Без проекта</option>'; });
const createOptions = document.getElementById('create-project-options');
if (createOptions) {
createOptions.innerHTML = '';
projectsCache.forEach(project => { projectsCache.forEach(project => {
projectNameByUUID[project.uuid] = project.name;
if (!project.is_active) return; if (!project.is_active) return;
const option = document.createElement('option'); const option = document.createElement('option');
option.value = project.uuid; option.value = project.name;
option.textContent = project.name; createOptions.appendChild(option);
select.appendChild(option);
}); });
} }
} catch (e) { } catch (e) {