From a9c86c6dad4b3eca2ed5246ac122c76d994409e9 Mon Sep 17 00:00:00 2001 From: s4luorth Date: Thu, 19 Feb 2026 10:22:44 +0100 Subject: [PATCH] fix, table cp, mengenfeld --- .../assets/js/configurator-app.js | 7 + .../assets/js/configurator-ui.js | 143 +++++++++++------- .../assets/js/price-calculator.js | 26 ++-- 3 files changed, 109 insertions(+), 67 deletions(-) diff --git a/skrift-configurator/assets/js/configurator-app.js b/skrift-configurator/assets/js/configurator-app.js index be4ce3a..6ef3ee4 100644 --- a/skrift-configurator/assets/js/configurator-app.js +++ b/skrift-configurator/assets/js/configurator-app.js @@ -13,6 +13,13 @@ import PreviewManager from './configurator-preview-manager.js'; // Preview Manag const root = document.querySelector('[data-skrift-konfigurator="1"]'); if (!root) return; + // Guard: Verhindere doppelte Initialisierung + if (root.dataset.skriftInitialized === '1') { + console.warn('[Konfigurator] Bereits initialisiert, überspringe.'); + return; + } + root.dataset.skriftInitialized = '1'; + // Cleanup bei Navigation (SPA) oder Seiten-Unload const cleanupHandlers = []; diff --git a/skrift-configurator/assets/js/configurator-ui.js b/skrift-configurator/assets/js/configurator-ui.js index 248d75e..6a50b16 100644 --- a/skrift-configurator/assets/js/configurator-ui.js +++ b/skrift-configurator/assets/js/configurator-ui.js @@ -3512,6 +3512,7 @@ function downloadText(filename, text) { function createTableModal(dispatch, config) { let tableWrapper; let currentState = config.getStateFunc(); + let isPasting = false; // Flag um Paste-Vorgang zu markieren // State-Referenz aktualisieren (wird nach jedem Dispatch aufgerufen) const updateState = () => { @@ -3585,6 +3586,9 @@ function createTableModal(dispatch, config) { oninput: col.readOnly ? null : (e) => { + // Während Paste-Vorgang ignorieren - Paste-Handler übernimmt + if (isPasting) return; + // Scroll-Position VOR dem dispatch speichern const modalContent = e.target.closest(".sk-modal-content"); const scrollTop = modalContent ? modalContent.scrollTop : 0; @@ -3633,15 +3637,26 @@ function createTableModal(dispatch, config) { // Excel Paste Handler mit automatischer Tabellen-Aktualisierung table.addEventListener("paste", (e) => { e.preventDefault(); + isPasting = true; // Flag setzen um oninput zu blockieren + const text = e.clipboardData.getData("text/plain"); - if (!text || !text.trim()) return; + if (!text || !text.trim()) { + isPasting = false; + return; + } const rows = text.split(/\r?\n/).filter((r) => r.trim()); - if (rows.length === 0) return; + if (rows.length === 0) { + isPasting = false; + return; + } const activeInput = document.activeElement; - if (!activeInput || !activeInput.classList.contains("sk-input")) return; + if (!activeInput || !activeInput.classList.contains("sk-input")) { + isPasting = false; + return; + } const startRow = parseInt(activeInput.dataset.row || "0"); const startCol = parseInt(activeInput.dataset.col || "0"); @@ -3683,6 +3698,11 @@ function createTableModal(dispatch, config) { // Tabelle im Modal aktualisieren renderTable(); } + + // Flag nach kurzem Delay zurücksetzen (für evtl. verzögerte input-Events) + setTimeout(() => { + isPasting = false; + }, 50); }); return table; @@ -4039,20 +4059,26 @@ function renderRecipientsTable(state, dispatch) { } table.appendChild(tbody); - // Focusout-Handler mit Debounce um Flackern zu vermeiden - let focusoutTimer = null; - let hasLocalChanges = false; // Track ob lokale Änderungen gemacht wurden - table.addEventListener("focusout", (e) => { - if (focusoutTimer) clearTimeout(focusoutTimer); - focusoutTimer = setTimeout(() => { - const newFocus = document.activeElement; - if (!table.contains(newFocus) && hasLocalChanges) { - // Nur dispatchen wenn lokale Änderungen gemacht wurden - dispatch({ type: "SET_RECIPIENT_ROWS", rows: [...table._rows] }); - hasLocalChanges = false; - } - }, 150); - }); + // Save-Funktion für Registry (wird bei Navigation und periodisch aufgerufen) + let hasLocalChanges = false; + const saveLocalChanges = () => { + if (hasLocalChanges) { + dispatch({ type: "SET_RECIPIENT_ROWS", rows: [...table._rows] }); + hasLocalChanges = false; + } + }; + + // Bei globaler Registry registrieren + const unregisterSave = registerTableSave(saveLocalChanges); + + // Auto-Save alle 60 Sekunden + const autoSaveInterval = setInterval(saveLocalChanges, 60000); + + // Cleanup-Funktion + table._cleanup = () => { + if (autoSaveInterval) clearInterval(autoSaveInterval); + unregisterSave(); + }; // Änderungen tracken bei Input table.addEventListener("input", () => { @@ -4129,6 +4155,7 @@ function renderRecipientsTable(state, dispatch) { }); if (pastedCells > 0) { + hasLocalChanges = false; // Paste übernimmt - keine alten Änderungen mehr relevant dispatch({ type: "SET_RECIPIENT_ROWS", rows: newData }); } }); @@ -4200,20 +4227,26 @@ function renderRecipientsTable(state, dispatch) { } table.appendChild(tbody); - // Focusout-Handler mit Debounce um Flackern zu vermeiden - let focusoutTimer = null; - let hasLocalChanges = false; // Track ob lokale Änderungen gemacht wurden - table.addEventListener("focusout", (e) => { - if (focusoutTimer) clearTimeout(focusoutTimer); - focusoutTimer = setTimeout(() => { - const newFocus = document.activeElement; - if (!table.contains(newFocus) && hasLocalChanges) { - // Nur dispatchen wenn lokale Änderungen gemacht wurden - dispatch({ type: "SET_FREE_ADDRESS_ROWS", rows: [...rows] }); - hasLocalChanges = false; - } - }, 150); - }); + // Save-Funktion für Registry (wird bei Navigation und periodisch aufgerufen) + let hasLocalChanges = false; + const saveLocalChanges = () => { + if (hasLocalChanges) { + dispatch({ type: "SET_FREE_ADDRESS_ROWS", rows: [...rows] }); + hasLocalChanges = false; + } + }; + + // Bei globaler Registry registrieren + const unregisterSave = registerTableSave(saveLocalChanges); + + // Auto-Save alle 60 Sekunden + const autoSaveInterval = setInterval(saveLocalChanges, 60000); + + // Cleanup-Funktion + table._cleanup = () => { + if (autoSaveInterval) clearInterval(autoSaveInterval); + unregisterSave(); + }; // Änderungen tracken bei Input table.addEventListener("input", () => { @@ -4272,6 +4305,7 @@ function renderRecipientsTable(state, dispatch) { }); if (pastedCells > 0) { + hasLocalChanges = false; // Paste übernimmt - keine alten Änderungen mehr relevant dispatch({ type: "SET_FREE_ADDRESS_ROWS", rows: newData }); } }); @@ -4561,18 +4595,6 @@ function renderCombinedPlaceholderTable( } table.appendChild(tbody); - // Focusout-Handler: Speichern wenn Fokus die Tabelle verlässt - let focusoutTimer = null; - table.addEventListener("focusout", (e) => { - if (focusoutTimer) clearTimeout(focusoutTimer); - focusoutTimer = setTimeout(() => { - const newFocus = document.activeElement; - if (!table.contains(newFocus)) { - saveLocalChanges(); - } - }, 150); - }); - // Paste-Handler für Excel-Daten table.addEventListener("paste", (e) => { e.preventDefault(); @@ -4814,20 +4836,26 @@ function renderPlaceholderTable( } table.appendChild(tbody); - // Focusout-Handler mit Debounce - analog zur Empfängertabelle - let focusoutTimer = null; - let hasLocalChanges = false; // Track ob lokale Änderungen gemacht wurden - table.addEventListener("focusout", (e) => { - if (focusoutTimer) clearTimeout(focusoutTimer); - focusoutTimer = setTimeout(() => { - const newFocus = document.activeElement; - if (!table.contains(newFocus) && hasLocalChanges) { - // Nur dispatchen wenn lokale Änderungen gemacht wurden - dispatch({ type: "SET_PLACEHOLDER_VALUES", values: localValues }); - hasLocalChanges = false; - } - }, 150); - }); + // Save-Funktion für Registry (wird bei Navigation und periodisch aufgerufen) + let hasLocalChanges = false; + const saveLocalChanges = () => { + if (hasLocalChanges) { + dispatch({ type: "SET_PLACEHOLDER_VALUES", values: localValues }); + hasLocalChanges = false; + } + }; + + // Bei globaler Registry registrieren + const unregisterSave = registerTableSave(saveLocalChanges); + + // Auto-Save alle 60 Sekunden + const autoSaveInterval = setInterval(saveLocalChanges, 60000); + + // Cleanup-Funktion + table._cleanup = () => { + if (autoSaveInterval) clearInterval(autoSaveInterval); + unregisterSave(); + }; // Änderungen tracken bei Input table.addEventListener("input", () => { @@ -4881,6 +4909,7 @@ function renderPlaceholderTable( // Einmal dispatch für alle Änderungen if (pastedCells > 0) { + hasLocalChanges = false; // Paste übernimmt - keine alten Änderungen mehr relevant dispatch({ type: "SET_PLACEHOLDER_VALUES", values: localValues }); } }); diff --git a/skrift-configurator/assets/js/price-calculator.js b/skrift-configurator/assets/js/price-calculator.js index 45cbe75..0f49c60 100644 --- a/skrift-configurator/assets/js/price-calculator.js +++ b/skrift-configurator/assets/js/price-calculator.js @@ -29,19 +29,19 @@ function createInitialState() { step: STEPS.PRODUCT, customerType: null, // 'business' | 'private' product: null, - // Calculator-Felder + // Calculator-Felder - Defaults auf günstigste Optionen quantity: 100, - format: 'a6h', // a4, a6h (a6p), a6q (a6l) - shippingMode: 'direct', // direct, bulk - envelope: true, // Bei Sammelversand: Kuvert ja/nein - envelopeMode: 'recipientData', // recipientData, customText, none (Beschriftungsart) + format: 'a6p', // a6p ist günstiger als a4 (kein Aufpreis) + shippingMode: 'bulk', // Sammelversand ist günstiger (nur Pauschale statt pro Stück) + envelope: false, // Kein Kuvert ist günstiger + envelopeMode: 'none', // Keine Beschriftung ist günstiger followupYearlyVolume: '50-199', - followupCreateMode: 'manual', // auto, manual + followupCreateMode: 'manual', // Manuell ist günstiger (keine API-Kosten) // Motiv (nur für Postkarten/Einladungen mit A6) - motifNeed: false, + motifNeed: false, // Kein Motiv ist günstiger motifSource: null, // upload, design // Inhalt - contentCreateMode: 'self', // self, textservice + contentCreateMode: 'self', // Selbst erstellen ist günstiger }; } @@ -55,8 +55,14 @@ function reducer(state, action) { }; case 'SET_PRODUCT': { - // Default-Format setzen - const defaultFormat = action.product.formats?.[0] || 'a4'; + // Default-Format: Günstigstes zuerst (A6 hat keinen Aufpreis bei Postkarten/Einladungen) + // Bei Produkten mit supportsMotif ist A6 günstiger, also bevorzugen + const formats = action.product.formats || []; + const hasA6 = formats.some(f => f === 'a6p' || f === 'a6l'); + const hasA4 = formats.includes('a4'); + // A6 bevorzugen wenn verfügbar (günstiger), sonst erstes Format + const defaultFormat = hasA6 ? 'a6p' : (formats[0] || 'a4'); + // Default-Menge je nach Kundentyp const settings = window.SkriftPreisrechner?.settings || window.SkriftConfigurator?.settings || {}; const dynamicPricing = settings.dynamic_pricing || {};