From bb0c388cb4d30347ddcfc8db9ec809a1158b153d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 29 May 2026 06:41:33 +0000 Subject: [PATCH] Refactor editItem to improve code health - Extracted DOM element creation to `createEditFormElements`. - Extracted API update logic to `updateItemApi`. - Extracted view restoration logic to `restoreItemView`. - Refactored `editItem` to act as an orchestrator for these helpers. - Removed unused `restoreView` internal function. Co-authored-by: deitaur <113350206+deitaur@users.noreply.github.com> --- public/script.js | 177 +++++++++++++++++++++++------------------------ 1 file changed, 85 insertions(+), 92 deletions(-) diff --git a/public/script.js b/public/script.js index bcf568f..086d2e9 100644 --- a/public/script.js +++ b/public/script.js @@ -326,33 +326,15 @@ async function deleteItem(id, level) { } } -async function editItem(item, level) { - const li = document.querySelector(`li[data-id="${item.id}"]`); - if (!li) return; - - const nameContainer = li.querySelector('div'); // контейнер с именем и типом - if (!nameContainer) return; - - // Найдём кнопку редактирования/контейнер действий (чтобы не потерять) - const buttonContainer = li.querySelector('.item-actions'); - - // Сохраняем старое состояние на случай отмены - const originalName = item.name; - const originalType = item.type || ''; - - // Очистим контейнер и создадим поля для редактирования - nameContainer.innerHTML = ''; - +function createEditFormElements(originalName, originalType, level) { const nameInput = document.createElement('input'); nameInput.type = 'text'; nameInput.value = originalName; nameInput.style.minWidth = '140px'; nameInput.className = 'inline-name-input'; - nameContainer.appendChild(nameInput); - - // Показываем селект типа только для объектов, у которых тип можно менять (не house/room) let typeSelect = null; + let typeSpan = null; const editableTypes = ['item', 'container', 'pocket', 'cabinet', 'shelf']; if (level !== 'house' && level !== 'room') { typeSelect = document.createElement('select'); @@ -364,17 +346,13 @@ async function editItem(item, level) { if (t === originalType) opt.selected = true; typeSelect.appendChild(opt); }); - nameContainer.appendChild(typeSelect); } else { - // если тип не редактируемый, показываем текущий тип как span - const typeSpan = document.createElement('span'); + typeSpan = document.createElement('span'); typeSpan.textContent = ` (${originalType || level})`; typeSpan.style.color = '#888'; typeSpan.style.marginLeft = '6px'; - nameContainer.appendChild(typeSpan); } - // Кнопки сохранить / отмена const saveBtn = document.createElement('button'); saveBtn.textContent = 'Сохранить'; saveBtn.className = 'save-inline-btn'; @@ -385,54 +363,90 @@ async function editItem(item, level) { cancelBtn.className = 'cancel-inline-btn'; cancelBtn.style.marginLeft = '4px'; - nameContainer.appendChild(saveBtn); - nameContainer.appendChild(cancelBtn); + return { nameInput, typeSelect, typeSpan, saveBtn, cancelBtn }; +} - // Фокус - nameInput.focus(); - nameInput.select(); +function restoreItemView(nameContainer, item, nameText, typeText) { + nameContainer.innerHTML = ''; + const link = document.createElement('a'); + link.textContent = nameText; + link.href = '#'; + link.dataset.id = item.id; + link.dataset.type = typeText; + link.style.textDecoration = 'none'; + link.style.color = ''; // используем стилевую систему страницы + + const itemTypeSpan = document.createElement('span'); + itemTypeSpan.textContent = ` (${typeText})`; + itemTypeSpan.style.color = '#888'; + itemTypeSpan.style.fontSize = '0.9em'; + itemTypeSpan.style.marginLeft = '6px'; + + nameContainer.appendChild(link); + nameContainer.appendChild(itemTypeSpan); + + const containerTypes = ['room','cabinet','container','pocket','shelf','house']; + if (containerTypes.includes(typeText)) { + link.classList.add('clickable-item'); + } else { + link.classList.remove('clickable-item'); + } +} - const makeNameAndTypeNodes = (nameText, typeText) => { - nameContainer.innerHTML = ''; // очистим перед восстановлением - const link = document.createElement('a'); - link.textContent = nameText; - link.href = '#'; - link.dataset.id = item.id; - link.dataset.type = typeText; - link.style.textDecoration = 'none'; - link.style.color = ''; // используем стилевую систему страницы +async function updateItemApi(id, level, payload) { + let url; + if (level === 'house') { + url = `/api/houses/${id}`; + } else if (level === 'room') { + url = `/api/rooms/${id}`; + } else { + url = `/api/items/${id}`; + } - const itemTypeSpan = document.createElement('span'); - itemTypeSpan.textContent = ` (${typeText})`; - itemTypeSpan.style.color = '#888'; - itemTypeSpan.style.fontSize = '0.9em'; - itemTypeSpan.style.marginLeft = '6px'; + const response = await fetch(url, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); - // Вставляем в контейнер - nameContainer.appendChild(link); - nameContainer.appendChild(itemTypeSpan); + if (!response.ok) { + const err = await response.json().catch(()=>({ error: 'Ошибка' })); + throw new Error(err.error || err.message || 'Ошибка при сохранении.'); + } + return response; +} - // Если тип — контейнер/компонент, оставляем ссылку кликабельной (handler уже есть на parent) - // Добавляем класс, чтобы CSS мог подсветить ссылки- контейнеры - const containerTypes = ['room','cabinet','container','pocket','shelf','house']; - if (containerTypes.includes(typeText)) { - link.classList.add('clickable-item'); - } else { - link.classList.remove('clickable-item'); - } - }; +async function editItem(item, level) { + const li = document.querySelector(`li[data-id="${item.id}"]`); + if (!li) return; - const restoreView = () => { - // Восстановим отображение в текущем контексте — но без перезагрузки данных, если уже обновили DOM - const lastStep = currentPath[currentPath.length - 1]; - if (!lastStep || lastStep.type === 'root') { - showHouses(); - } else if (lastStep.type === 'house') { - showRooms(lastStep.id, lastStep.name); - } else { - showItems(lastStep.id, lastStep.name, lastStep.type); - } - }; + const nameContainer = li.querySelector('div'); // контейнер с именем и типом + if (!nameContainer) return; + + // Найдём кнопку редактирования/контейнер действий (чтобы не потерять) + const buttonContainer = li.querySelector('.item-actions'); + + // Сохраняем старое состояние на случай отмены + const originalName = item.name; + const originalType = item.type || ''; + + // Очистим контейнер и создадим поля для редактирования + nameContainer.innerHTML = ''; + + const { nameInput, typeSelect, typeSpan, saveBtn, cancelBtn } = createEditFormElements(originalName, originalType, level); + + nameContainer.appendChild(nameInput); + if (typeSelect) { + nameContainer.appendChild(typeSelect); + } else if (typeSpan) { + nameContainer.appendChild(typeSpan); + } + nameContainer.appendChild(saveBtn); + nameContainer.appendChild(cancelBtn); + + // Фокус + nameInput.focus(); + nameInput.select(); const handleSave = async () => { const newName = nameInput.value.trim(); @@ -444,34 +458,16 @@ async function editItem(item, level) { } try { - let url; - if (level === 'house') { - url = `/api/houses/${item.id}`; - } else if (level === 'room') { - url = `/api/rooms/${item.id}`; - } else { - url = `/api/items/${item.id}`; - } - const payload = { name: newName }; if (typeSelect) payload.type = newType; - const response = await fetch(url, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload) - }); - - if (!response.ok) { - const err = await response.json().catch(()=>({ error: 'Ошибка' })); - throw new Error(err.error || err.message || 'Ошибка при сохранении.'); - } + await updateItemApi(item.id, level, payload); // Оптимистичное обновление: обновим локальный объект и DOM без полной перезагрузки item.name = newName; if (typeSelect) item.type = newType; - makeNameAndTypeNodes(newName, item.type || level); + restoreItemView(nameContainer, item, newName, item.type || level); // Кнопки остаются в buttonContainer, ничего не трогаем // Слегка подсветим изменения @@ -480,20 +476,17 @@ async function editItem(item, level) { buttonContainer.style.background = 'rgba(200,255,200,0.3)'; setTimeout(()=> buttonContainer.style.background = '', 600); } - - // Если тип стал контейнером — можно сразу открыть его при клике (click handler на parent обработает) - return; } catch (error) { console.error(error); if (errorMessageEl) errorMessageEl.textContent = error.message || 'Ошибка при сохранении.'; // В случае ошибки восстановим прежний вид - makeNameAndTypeNodes(originalName, originalType || level); + restoreItemView(nameContainer, item, originalName, originalType || level); } }; const handleCancel = () => { // Восстановим исходный вид - makeNameAndTypeNodes(originalName, originalType || level); + restoreItemView(nameContainer, item, originalName, originalType || level); }; saveBtn.addEventListener('click', (e) => { e.preventDefault(); handleSave(); });