Deduplicate configuration revisions and update revisions UI
This commit is contained in:
@@ -401,18 +401,28 @@ function updateConfigBreadcrumbs() {
|
||||
}
|
||||
codeEl.textContent = code;
|
||||
variantEl.textContent = variant;
|
||||
configEl.textContent = configName || 'Конфигурация';
|
||||
const fullConfigName = configName || 'Конфигурация';
|
||||
configEl.textContent = truncateBreadcrumbSpecName(fullConfigName);
|
||||
configEl.title = fullConfigName;
|
||||
versionEl.textContent = 'v' + (currentVersionNo || 1);
|
||||
const configNameLinkEl = document.getElementById('breadcrumb-config-name-link');
|
||||
if (configNameLinkEl && configUUID) {
|
||||
configNameLinkEl.href = '/configs/' + configUUID + '/revisions';
|
||||
}
|
||||
}
|
||||
|
||||
function truncateBreadcrumbSpecName(name) {
|
||||
const maxLength = 16;
|
||||
if (!name || name.length <= maxLength) return name;
|
||||
return name.slice(0, maxLength - 1) + '…';
|
||||
}
|
||||
let currentTab = 'base';
|
||||
let allComponents = [];
|
||||
let cart = [];
|
||||
let categoryOrderMap = {}; // Category code -> display_order mapping
|
||||
let autoSaveTimeout = null; // Timeout for debounced autosave
|
||||
let hasUnsavedChanges = false;
|
||||
let exitSaveStarted = false;
|
||||
let serverCount = 1; // Server count for the configuration
|
||||
let serverModelForQuote = '';
|
||||
let supportCode = '';
|
||||
@@ -1890,13 +1900,128 @@ function getCurrentArticle() {
|
||||
return currentArticle || '';
|
||||
}
|
||||
|
||||
function getAutosaveStorageKey() {
|
||||
return `qf_config_autosave_${configUUID || 'default'}`;
|
||||
}
|
||||
|
||||
function buildSavePayload() {
|
||||
const customPriceInput = document.getElementById('custom-price-input');
|
||||
const customPriceValue = parseFloat(customPriceInput.value);
|
||||
const customPrice = customPriceValue > 0 ? customPriceValue : null;
|
||||
|
||||
return {
|
||||
name: configName,
|
||||
items: cart,
|
||||
custom_price: customPrice,
|
||||
notes: '',
|
||||
server_count: serverCount,
|
||||
server_model: serverModelForQuote,
|
||||
support_code: supportCode,
|
||||
article: getCurrentArticle(),
|
||||
pricelist_id: selectedPricelistIds.estimate,
|
||||
only_in_stock: onlyInStock
|
||||
};
|
||||
}
|
||||
|
||||
function persistAutosaveDraft() {
|
||||
if (!configUUID) return;
|
||||
try {
|
||||
sessionStorage.setItem(getAutosaveStorageKey(), JSON.stringify({
|
||||
payload: buildSavePayload(),
|
||||
saved_at: Date.now()
|
||||
}));
|
||||
} catch (_) {
|
||||
// ignore storage failures
|
||||
}
|
||||
}
|
||||
|
||||
function clearAutosaveDraft() {
|
||||
try {
|
||||
sessionStorage.removeItem(getAutosaveStorageKey());
|
||||
} catch (_) {
|
||||
// ignore storage failures
|
||||
}
|
||||
}
|
||||
|
||||
function restoreAutosaveDraftIfAny() {
|
||||
if (!configUUID) return;
|
||||
let raw = null;
|
||||
try {
|
||||
raw = sessionStorage.getItem(getAutosaveStorageKey());
|
||||
} catch (_) {
|
||||
raw = null;
|
||||
}
|
||||
if (!raw) return;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
const payload = parsed && parsed.payload ? parsed.payload : null;
|
||||
if (!payload) return;
|
||||
|
||||
if (Array.isArray(payload.items)) {
|
||||
cart = payload.items.map(item => ({
|
||||
lot_name: item.lot_name,
|
||||
quantity: item.quantity,
|
||||
unit_price: item.unit_price,
|
||||
estimate_price: item.unit_price,
|
||||
warehouse_price: null,
|
||||
competitor_price: null,
|
||||
description: item.description || '',
|
||||
category: item.category || getCategoryFromLotName(item.lot_name)
|
||||
}));
|
||||
}
|
||||
if (typeof payload.server_count === 'number' && payload.server_count > 0) {
|
||||
serverCount = payload.server_count;
|
||||
const serverCountInput = document.getElementById('server-count');
|
||||
if (serverCountInput) serverCountInput.value = serverCount;
|
||||
const totalServerCount = document.getElementById('total-server-count');
|
||||
if (totalServerCount) totalServerCount.textContent = serverCount;
|
||||
}
|
||||
serverModelForQuote = payload.server_model || serverModelForQuote;
|
||||
supportCode = payload.support_code || supportCode;
|
||||
currentArticle = payload.article || currentArticle;
|
||||
selectedPricelistIds.estimate = payload.pricelist_id || selectedPricelistIds.estimate;
|
||||
onlyInStock = Boolean(payload.only_in_stock);
|
||||
|
||||
const customPriceInput = document.getElementById('custom-price-input');
|
||||
if (customPriceInput) {
|
||||
if (typeof payload.custom_price === 'number' && payload.custom_price > 0) {
|
||||
customPriceInput.value = payload.custom_price.toFixed(2);
|
||||
} else {
|
||||
customPriceInput.value = '';
|
||||
}
|
||||
}
|
||||
hasUnsavedChanges = true;
|
||||
} catch (_) {
|
||||
// ignore invalid draft
|
||||
}
|
||||
}
|
||||
|
||||
function saveConfigOnExit() {
|
||||
if (!configUUID || !hasUnsavedChanges || exitSaveStarted) return;
|
||||
exitSaveStarted = true;
|
||||
try {
|
||||
fetch('/api/configs/' + configUUID, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(buildSavePayload()),
|
||||
keepalive: true
|
||||
});
|
||||
} catch (_) {
|
||||
// best effort save on page exit
|
||||
}
|
||||
}
|
||||
|
||||
function triggerAutoSave() {
|
||||
// Debounce autosave - wait 1 second after last change
|
||||
// Autosave keeps local draft only; server revision is created on Save/Exit.
|
||||
hasUnsavedChanges = true;
|
||||
if (autoSaveTimeout) {
|
||||
clearTimeout(autoSaveTimeout);
|
||||
}
|
||||
autoSaveTimeout = setTimeout(() => {
|
||||
saveConfig(false); // false = don't show notification
|
||||
persistAutosaveDraft();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
@@ -1910,32 +2035,13 @@ async function saveConfig(showNotification = true) {
|
||||
|
||||
await refreshPriceLevels({ force: true, noCache: true });
|
||||
|
||||
// Get custom price if set
|
||||
const customPriceInput = document.getElementById('custom-price-input');
|
||||
const customPriceValue = parseFloat(customPriceInput.value);
|
||||
const customPrice = customPriceValue > 0 ? customPriceValue : null;
|
||||
|
||||
// Get server count
|
||||
const serverCountValue = serverCount;
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/configs/' + configUUID, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: configName,
|
||||
items: cart,
|
||||
custom_price: customPrice,
|
||||
notes: '',
|
||||
server_count: serverCountValue,
|
||||
server_model: serverModelForQuote,
|
||||
support_code: supportCode,
|
||||
article: getCurrentArticle(),
|
||||
pricelist_id: selectedPricelistIds.estimate,
|
||||
only_in_stock: onlyInStock
|
||||
})
|
||||
body: JSON.stringify(buildSavePayload())
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
@@ -1951,6 +2057,9 @@ async function saveConfig(showNotification = true) {
|
||||
const versionEl = document.getElementById('breadcrumb-config-version');
|
||||
if (versionEl) versionEl.textContent = 'v' + currentVersionNo;
|
||||
}
|
||||
hasUnsavedChanges = false;
|
||||
clearAutosaveDraft();
|
||||
exitSaveStarted = false;
|
||||
|
||||
if (showNotification) {
|
||||
showToast('Сохранено', 'success');
|
||||
|
||||
Reference in New Issue
Block a user