Add source popup reload action

This commit is contained in:
2026-08-05 19:44:11 +02:00
parent e172b24474
commit 1f5ab5a8cc
6 changed files with 19 additions and 6 deletions
+1
View File
@@ -147,6 +147,7 @@ There are three selectable data sources:
- `Get product info` remains a separate action. - `Get product info` remains a separate action.
- EAN scan input searches on Enter. - EAN scan input searches on Enter.
- `Find sources` searches once and caches candidates for the current product; a second click reopens cached results. - `Find sources` searches once and caches candidates for the current product; a second click reopens cached results.
- The source-candidates popup must provide a `Reload` action beside `Close`; `Reload` bypasses only the current product's cached source result and re-runs the source lookup using the current local mapping configuration. It must not switch database modes or write to Live DB.
- Source candidates show how they were found, including EAN/reference/name and color context. - Source candidates show how they were found, including EAN/reference/name and color context.
- Each source row has its own `Get pictures` action. - Each source row has its own `Get pictures` action.
- Product source cache is cleared when a new product loads. - Product source cache is cleared when a new product loads.
+6 -2
View File
@@ -34,6 +34,7 @@ const sidebarOverlay = document.querySelector("#sidebarOverlay");
const sourceModal = document.querySelector("#sourceModal"); const sourceModal = document.querySelector("#sourceModal");
const sourceTitle = document.querySelector("#sourceTitle"); const sourceTitle = document.querySelector("#sourceTitle");
const sourceList = document.querySelector("#sourceList"); const sourceList = document.querySelector("#sourceList");
const sourceReloadButton = document.querySelector("#sourceReloadButton");
const sourceCloseButton = document.querySelector("#sourceCloseButton"); const sourceCloseButton = document.querySelector("#sourceCloseButton");
const settingsButton = document.querySelector("#settingsButton"); const settingsButton = document.querySelector("#settingsButton");
const themeToggle = document.querySelector("#themeToggle"); const themeToggle = document.querySelector("#themeToggle");
@@ -185,6 +186,7 @@ loadProductButton.disabled = true;
loadProductButton.title = "Load existing product attribute info."; loadProductButton.title = "Load existing product attribute info.";
loadProductButton.addEventListener("click", loadProductInfoForCurrentProduct); loadProductButton.addEventListener("click", loadProductInfoForCurrentProduct);
findSourcesButton.addEventListener("click", findSourcesForCurrentProduct); findSourcesButton.addEventListener("click", findSourcesForCurrentProduct);
sourceReloadButton.addEventListener("click", () => findSourcesForCurrentProduct(true));
getPicturesButton.addEventListener("click", getPicturesForCurrentProduct); getPicturesButton.addEventListener("click", getPicturesForCurrentProduct);
secondaryPanelToggle.addEventListener("click", toggleSecondaryPanel); secondaryPanelToggle.addEventListener("click", toggleSecondaryPanel);
mainWorkspaceToggle.addEventListener("click", toggleMainWorkspace); mainWorkspaceToggle.addEventListener("click", toggleMainWorkspace);
@@ -646,13 +648,13 @@ function closeSuggestedProducts() {
suggestedModal.hidden = true; suggestedModal.hidden = true;
suggestedList.replaceChildren(); suggestedList.replaceChildren();
} }
async function findSourcesForCurrentProduct() { async function findSourcesForCurrentProduct(forceRefresh = false) {
if (!manufacturerSelect.value || !currentProduct) { if (!manufacturerSelect.value || !currentProduct) {
setTableMessage("Select manufacturer and product first."); setTableMessage("Select manufacturer and product first.");
return; return;
} }
const savedDraft = readLocalDraft(); const savedDraft = readLocalDraft();
if (savedDraft?.sources) { if (savedDraft?.sources && !forceRefresh) {
renderSourceCandidates(savedDraft.sources.items || []); renderSourceCandidates(savedDraft.sources.items || []);
updateFindSourcesButtonState(); updateFindSourcesButtonState();
sourceTitle.textContent = "Source candidates (cached)"; sourceTitle.textContent = "Source candidates (cached)";
@@ -661,6 +663,7 @@ async function findSourcesForCurrentProduct() {
} }
const supplierColorGroup = getFirstSupplierColorGroup(); const supplierColorGroup = getFirstSupplierColorGroup();
findSourcesButton.disabled = true; findSourcesButton.disabled = true;
sourceReloadButton.disabled = true;
setButtonLabel(findSourcesButton, "Finding sources..."); setButtonLabel(findSourcesButton, "Finding sources...");
try { try {
const params = new URLSearchParams({ const params = new URLSearchParams({
@@ -689,6 +692,7 @@ async function findSourcesForCurrentProduct() {
setTableMessage(error.message || "Source search failed."); setTableMessage(error.message || "Source search failed.");
} }
finally { finally {
sourceReloadButton.disabled = false;
updateFindSourcesButtonState(); updateFindSourcesButtonState();
} }
} }
+6 -2
View File
@@ -34,6 +34,7 @@ const sidebarOverlay = document.querySelector("#sidebarOverlay");
const sourceModal = document.querySelector("#sourceModal"); const sourceModal = document.querySelector("#sourceModal");
const sourceTitle = document.querySelector("#sourceTitle"); const sourceTitle = document.querySelector("#sourceTitle");
const sourceList = document.querySelector("#sourceList"); const sourceList = document.querySelector("#sourceList");
const sourceReloadButton = document.querySelector("#sourceReloadButton");
const sourceCloseButton = document.querySelector("#sourceCloseButton"); const sourceCloseButton = document.querySelector("#sourceCloseButton");
const settingsButton = document.querySelector("#settingsButton"); const settingsButton = document.querySelector("#settingsButton");
const themeToggle = document.querySelector("#themeToggle"); const themeToggle = document.querySelector("#themeToggle");
@@ -190,6 +191,7 @@ loadProductButton.disabled = true;
loadProductButton.title = "Load existing product attribute info."; loadProductButton.title = "Load existing product attribute info.";
loadProductButton.addEventListener("click", loadProductInfoForCurrentProduct); loadProductButton.addEventListener("click", loadProductInfoForCurrentProduct);
findSourcesButton.addEventListener("click", findSourcesForCurrentProduct); findSourcesButton.addEventListener("click", findSourcesForCurrentProduct);
sourceReloadButton.addEventListener("click", () => findSourcesForCurrentProduct(true));
getPicturesButton.addEventListener("click", getPicturesForCurrentProduct); getPicturesButton.addEventListener("click", getPicturesForCurrentProduct);
secondaryPanelToggle.addEventListener("click", toggleSecondaryPanel); secondaryPanelToggle.addEventListener("click", toggleSecondaryPanel);
mainWorkspaceToggle.addEventListener("click", toggleMainWorkspace); mainWorkspaceToggle.addEventListener("click", toggleMainWorkspace);
@@ -700,14 +702,14 @@ function closeSuggestedProducts() {
suggestedList.replaceChildren(); suggestedList.replaceChildren();
} }
async function findSourcesForCurrentProduct() { async function findSourcesForCurrentProduct(forceRefresh = false) {
if (!manufacturerSelect.value || !currentProduct) { if (!manufacturerSelect.value || !currentProduct) {
setTableMessage("Select manufacturer and product first."); setTableMessage("Select manufacturer and product first.");
return; return;
} }
const savedDraft = readLocalDraft(); const savedDraft = readLocalDraft();
if (savedDraft?.sources) { if (savedDraft?.sources && !forceRefresh) {
renderSourceCandidates(savedDraft.sources.items || []); renderSourceCandidates(savedDraft.sources.items || []);
updateFindSourcesButtonState(); updateFindSourcesButtonState();
sourceTitle.textContent = "Source candidates (cached)"; sourceTitle.textContent = "Source candidates (cached)";
@@ -717,6 +719,7 @@ async function findSourcesForCurrentProduct() {
const supplierColorGroup = getFirstSupplierColorGroup(); const supplierColorGroup = getFirstSupplierColorGroup();
findSourcesButton.disabled = true; findSourcesButton.disabled = true;
sourceReloadButton.disabled = true;
setButtonLabel(findSourcesButton, "Finding sources..."); setButtonLabel(findSourcesButton, "Finding sources...");
try { try {
@@ -746,6 +749,7 @@ async function findSourcesForCurrentProduct() {
} catch (error) { } catch (error) {
setTableMessage(error.message || "Source search failed."); setTableMessage(error.message || "Source search failed.");
} finally { } finally {
sourceReloadButton.disabled = false;
updateFindSourcesButtonState(); updateFindSourcesButtonState();
} }
} }
+4 -1
View File
@@ -146,7 +146,10 @@
<section class="source-dialog" aria-labelledby="sourceTitle"> <section class="source-dialog" aria-labelledby="sourceTitle">
<header class="suggested-header"> <header class="suggested-header">
<h2 id="sourceTitle">Source candidates</h2> <h2 id="sourceTitle">Source candidates</h2>
<button class="modal-close" id="sourceCloseButton" type="button"><svg class="sidebar-icon" aria-hidden="true"><use href="/icons.svg#close"></use></svg><span>Close</span></button> <div class="source-dialog-actions">
<button class="modal-close" id="sourceReloadButton" type="button"><svg class="sidebar-icon" aria-hidden="true"><use href="/icons.svg#refresh"></use></svg><span>Reload</span></button>
<button class="modal-close" id="sourceCloseButton" type="button"><svg class="sidebar-icon" aria-hidden="true"><use href="/icons.svg#close"></use></svg><span>Close</span></button>
</div>
</header> </header>
<div class="source-list" id="sourceList"></div> <div class="source-list" id="sourceList"></div>
</section> </section>
+1 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -182,6 +182,7 @@
.modal-backdrop { @apply fixed inset-0 z-50 flex items-center justify-center bg-slate-900/50 p-4; } .modal-backdrop { @apply fixed inset-0 z-50 flex items-center justify-center bg-slate-900/50 p-4; }
.modal-backdrop[hidden] { display: none; } .modal-backdrop[hidden] { display: none; }
.suggested-dialog, .source-dialog, .settings-dialog { @apply max-h-[90vh] w-full overflow-hidden rounded-lg border border-slate-300 bg-slate-50 shadow-2xl; } .suggested-dialog, .source-dialog, .settings-dialog { @apply max-h-[90vh] w-full overflow-hidden rounded-lg border border-slate-300 bg-slate-50 shadow-2xl; }
.source-dialog-actions { @apply flex items-center gap-1.5; }
.suggested-dialog { @apply max-w-2xl; } .suggested-dialog { @apply max-w-2xl; }
.source-dialog { @apply max-w-3xl; } .source-dialog { @apply max-w-3xl; }
.settings-dialog { @apply max-w-md; } .settings-dialog { @apply max-w-md; }