Upload Neu
This commit is contained in:
@@ -23,9 +23,6 @@ async function finalizeOrder(req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[Order] Finalizing order: ${orderNumber} from session: ${sessionId}`);
|
console.log(`[Order] Finalizing order: ${orderNumber} from session: ${sessionId}`);
|
||||||
if (envelopes && envelopes.length > 0) {
|
|
||||||
console.log(`[Order] Envelope data provided: ${envelopes.length} envelopes`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if session cache exists
|
// Check if session cache exists
|
||||||
const sessionDir = path.join(config.paths.previews, sessionId);
|
const sessionDir = path.join(config.paths.previews, sessionId);
|
||||||
@@ -97,6 +94,7 @@ async function finalizeOrder(req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create order metadata
|
// Create order metadata
|
||||||
|
const meta = req.body.metadata || {};
|
||||||
const orderMetadata = {
|
const orderMetadata = {
|
||||||
orderNumber,
|
orderNumber,
|
||||||
sessionId,
|
sessionId,
|
||||||
@@ -105,7 +103,10 @@ async function finalizeOrder(req, res, next) {
|
|||||||
envelopeCount: copiedEnvelopes.length,
|
envelopeCount: copiedEnvelopes.length,
|
||||||
letters: copiedLetters,
|
letters: copiedLetters,
|
||||||
envelopes: copiedEnvelopes,
|
envelopes: copiedEnvelopes,
|
||||||
other: copiedOther
|
other: copiedOther,
|
||||||
|
letterFormat: meta.letterFormat || null,
|
||||||
|
envelopeFormat: meta.envelopeFormat || null,
|
||||||
|
envelopeBeschriftung: meta.envelopeBeschriftung || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const orderMetadataPath = path.join(outputDir, 'order-metadata.json');
|
const orderMetadataPath = path.join(outputDir, 'order-metadata.json');
|
||||||
@@ -317,6 +318,7 @@ async function generateOrder(req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Step 6: Create order metadata
|
// Step 6: Create order metadata
|
||||||
|
const meta = req.body.metadata || {};
|
||||||
const orderMetadata = {
|
const orderMetadata = {
|
||||||
orderNumber,
|
orderNumber,
|
||||||
generatedAt: new Date().toISOString(),
|
generatedAt: new Date().toISOString(),
|
||||||
@@ -326,7 +328,10 @@ async function generateOrder(req, res, next) {
|
|||||||
envelopes: generatedEnvelopes,
|
envelopes: generatedEnvelopes,
|
||||||
fonts: Object.keys(lettersByFont),
|
fonts: Object.keys(lettersByFont),
|
||||||
hasPlaceholders,
|
hasPlaceholders,
|
||||||
csvFiles
|
csvFiles,
|
||||||
|
letterFormat: meta.letterFormat || null,
|
||||||
|
envelopeFormat: meta.envelopeFormat || null,
|
||||||
|
envelopeBeschriftung: meta.envelopeBeschriftung || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const orderMetadataPath = path.join(outputDir, 'order-metadata.json');
|
const orderMetadataPath = path.join(outputDir, 'order-metadata.json');
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const FONT_SIZE_PX = 26;
|
|||||||
const LINE_LIMITS = {
|
const LINE_LIMITS = {
|
||||||
a4: 27,
|
a4: 27,
|
||||||
a6p: 14,
|
a6p: 14,
|
||||||
a6l: 9,
|
a6l: 11,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Layout Konfiguration pro Format + Orientation
|
// Layout Konfiguration pro Format + Orientation
|
||||||
@@ -19,15 +19,16 @@ const FORMAT_LAYOUTS = {
|
|||||||
|
|
||||||
// A6 Hochformat
|
// A6 Hochformat
|
||||||
a6p: {
|
a6p: {
|
||||||
marginTopMm: 12,
|
marginTopMm: 8,
|
||||||
marginLeftMm: 10,
|
marginLeftMm: 8,
|
||||||
lineHeightFactor: 1.3,
|
lineHeightFactor: 1.3,
|
||||||
},
|
},
|
||||||
|
|
||||||
// A6 Querformat (landscape)
|
// A6 Querformat (landscape)
|
||||||
a6l: {
|
a6l: {
|
||||||
marginTopMm: 10,
|
marginTopMm: 8,
|
||||||
marginLeftMm: 8,
|
marginLeftMm: 8,
|
||||||
|
marginRightMm: 14,
|
||||||
lineHeightFactor: 1.2,
|
lineHeightFactor: 1.2,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -77,6 +78,7 @@ function getLayoutForFormat(format) {
|
|||||||
return {
|
return {
|
||||||
marginTopPx: mmToPx(layoutConfig.marginTopMm),
|
marginTopPx: mmToPx(layoutConfig.marginTopMm),
|
||||||
marginLeftPx: mmToPx(layoutConfig.marginLeftMm),
|
marginLeftPx: mmToPx(layoutConfig.marginLeftMm),
|
||||||
|
marginRightPx: layoutConfig.marginRightMm ? mmToPx(layoutConfig.marginRightMm) : null,
|
||||||
lineHeightPx: FONT_SIZE_PX * layoutConfig.lineHeightFactor,
|
lineHeightPx: FONT_SIZE_PX * layoutConfig.lineHeightFactor,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ function generateLetterSVG(text, format, options = {}) {
|
|||||||
const fontConfig = SVG_FONT_CONFIG[fontKey];
|
const fontConfig = SVG_FONT_CONFIG[fontKey];
|
||||||
|
|
||||||
const { widthPx, heightPx, widthMm, heightMm } = getPageDimensions(format);
|
const { widthPx, heightPx, widthMm, heightMm } = getPageDimensions(format);
|
||||||
const { marginTopPx, marginLeftPx, lineHeightPx } = getLayoutForFormat(format);
|
const { marginTopPx, marginLeftPx, marginRightPx, lineHeightPx } = getLayoutForFormat(format);
|
||||||
|
|
||||||
// Automatischer Zeilenumbruch
|
// Automatischer Zeilenumbruch
|
||||||
const maxWidthPx = widthPx - 2 * marginLeftPx;
|
const maxWidthPx = widthPx - marginLeftPx - (marginRightPx ?? marginLeftPx);
|
||||||
|
|
||||||
const lines = wrapTextToLinesByWidth({
|
const lines = wrapTextToLinesByWidth({
|
||||||
text: String(text || ''),
|
text: String(text || ''),
|
||||||
|
|||||||
@@ -4,28 +4,35 @@ import {
|
|||||||
deriveContextFromUrl,
|
deriveContextFromUrl,
|
||||||
reducer,
|
reducer,
|
||||||
STEPS,
|
STEPS,
|
||||||
} from "./configurator-state.js?ver=0.3.0";
|
} from "./configurator-state.js?ver=0.3.1";
|
||||||
import { render, showValidationOverlay, hideValidationOverlay, showOverflowWarning, showValidationError, flushAllTables } from "./configurator-ui.js?ver=0.3.0";
|
import {
|
||||||
import './configurator-api.js'; // Backend API initialisieren
|
render,
|
||||||
import PreviewManager from './configurator-preview-manager.js'; // Preview Management
|
showValidationOverlay,
|
||||||
|
hideValidationOverlay,
|
||||||
|
showOverflowWarning,
|
||||||
|
showValidationError,
|
||||||
|
flushAllTables,
|
||||||
|
} from "./configurator-ui.js?ver=0.3.1";
|
||||||
|
import "./configurator-api.js"; // Backend API initialisieren
|
||||||
|
import PreviewManager from "./configurator-preview-manager.js"; // Preview Management
|
||||||
|
|
||||||
(function boot() {
|
(function boot() {
|
||||||
const root = document.querySelector('[data-skrift-konfigurator="1"]');
|
const root = document.querySelector('[data-skrift-konfigurator="1"]');
|
||||||
if (!root) return;
|
if (!root) return;
|
||||||
|
|
||||||
// Guard: Verhindere doppelte Initialisierung
|
// Guard: Verhindere doppelte Initialisierung
|
||||||
if (root.dataset.skriftInitialized === '1') {
|
if (root.dataset.skriftInitialized === "1") {
|
||||||
console.warn('[Konfigurator] Bereits initialisiert, überspringe.');
|
console.warn("[Konfigurator] Bereits initialisiert, überspringe.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
root.dataset.skriftInitialized = '1';
|
root.dataset.skriftInitialized = "1";
|
||||||
|
|
||||||
// Cleanup bei Navigation (SPA) oder Seiten-Unload
|
// Cleanup bei Navigation (SPA) oder Seiten-Unload
|
||||||
const cleanupHandlers = [];
|
const cleanupHandlers = [];
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
flushAllTables(); // Tabellen-Daten speichern vor Cleanup/Seiten-Unload
|
flushAllTables(); // Tabellen-Daten speichern vor Cleanup/Seiten-Unload
|
||||||
cleanupHandlers.forEach(handler => handler());
|
cleanupHandlers.forEach((handler) => handler());
|
||||||
cleanupHandlers.length = 0;
|
cleanupHandlers.length = 0;
|
||||||
if (window.envelopePreviewManager) {
|
if (window.envelopePreviewManager) {
|
||||||
window.envelopePreviewManager.destroy();
|
window.envelopePreviewManager.destroy();
|
||||||
@@ -36,8 +43,10 @@ import PreviewManager from './configurator-preview-manager.js'; // Preview Manag
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Cleanup bei Seiten-Unload
|
// Cleanup bei Seiten-Unload
|
||||||
window.addEventListener('beforeunload', cleanup);
|
window.addEventListener("beforeunload", cleanup);
|
||||||
cleanupHandlers.push(() => window.removeEventListener('beforeunload', cleanup));
|
cleanupHandlers.push(() =>
|
||||||
|
window.removeEventListener("beforeunload", cleanup),
|
||||||
|
);
|
||||||
|
|
||||||
const ctx = deriveContextFromUrl(window.location.search);
|
const ctx = deriveContextFromUrl(window.location.search);
|
||||||
let state = createInitialState(ctx);
|
let state = createInitialState(ctx);
|
||||||
@@ -70,7 +79,7 @@ import PreviewManager from './configurator-preview-manager.js'; // Preview Manag
|
|||||||
|
|
||||||
// Scroll-Position NACH dem Render wiederherstellen
|
// Scroll-Position NACH dem Render wiederherstellen
|
||||||
// Nur bei Actions die NICHT den Step wechseln (Navigation)
|
// Nur bei Actions die NICHT den Step wechseln (Navigation)
|
||||||
const navigationActions = ['NAV_NEXT', 'NAV_PREV', 'SET_STEP'];
|
const navigationActions = ["NAV_NEXT", "NAV_PREV", "SET_STEP"];
|
||||||
if (!navigationActions.includes(action.type)) {
|
if (!navigationActions.includes(action.type)) {
|
||||||
// requestAnimationFrame stellt sicher, dass das DOM komplett gerendert ist
|
// requestAnimationFrame stellt sicher, dass das DOM komplett gerendert ist
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@@ -89,6 +98,21 @@ import PreviewManager from './configurator-preview-manager.js'; // Preview Manag
|
|||||||
const nextClickHandler = async () => {
|
const nextClickHandler = async () => {
|
||||||
flushAllTables(); // Tabellen-Daten speichern vor Navigation
|
flushAllTables(); // Tabellen-Daten speichern vor Navigation
|
||||||
|
|
||||||
|
// WICHTIG: Globalen State in den Store synchronisieren
|
||||||
|
// Nötig weil Paste direkt in window.currentGlobalState schreibt (Performance)
|
||||||
|
const globalState = window.currentGlobalState;
|
||||||
|
if (globalState) {
|
||||||
|
// Alle Tabellen-Daten in einem einzigen Dispatch synchronisieren
|
||||||
|
dispatch({
|
||||||
|
type: "SYNC_GLOBAL_STATE",
|
||||||
|
payload: {
|
||||||
|
recipientRows: globalState.recipientRows,
|
||||||
|
freeAddressRows: globalState.freeAddressRows,
|
||||||
|
placeholderValues: globalState.placeholderValues,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// WICHTIG: Aktuellen State verwenden, nicht den gecachten aus dem Closure
|
// WICHTIG: Aktuellen State verwenden, nicht den gecachten aus dem Closure
|
||||||
const currentState = window.currentGlobalState || state;
|
const currentState = window.currentGlobalState || state;
|
||||||
|
|
||||||
@@ -98,12 +122,16 @@ import PreviewManager from './configurator-preview-manager.js'; // Preview Manag
|
|||||||
if (previewManager) {
|
if (previewManager) {
|
||||||
showValidationOverlay();
|
showValidationOverlay();
|
||||||
try {
|
try {
|
||||||
const validation = await previewManager.validateTextLength(currentState);
|
const validation =
|
||||||
|
await previewManager.validateTextLength(currentState);
|
||||||
|
|
||||||
if (!validation.valid) {
|
if (!validation.valid) {
|
||||||
hideValidationOverlay();
|
hideValidationOverlay();
|
||||||
// Bei Overflow: Warnung anzeigen
|
// Bei Overflow: Warnung anzeigen
|
||||||
if (validation.overflowFiles && validation.overflowFiles.length > 0) {
|
if (
|
||||||
|
validation.overflowFiles &&
|
||||||
|
validation.overflowFiles.length > 0
|
||||||
|
) {
|
||||||
showOverflowWarning(validation.overflowFiles, dom.form);
|
showOverflowWarning(validation.overflowFiles, dom.form);
|
||||||
} else if (validation.error) {
|
} else if (validation.error) {
|
||||||
// Bei Fehler (z.B. keine Anfragen mehr): Fehlermeldung anzeigen
|
// Bei Fehler (z.B. keine Anfragen mehr): Fehlermeldung anzeigen
|
||||||
@@ -117,11 +145,15 @@ import PreviewManager from './configurator-preview-manager.js'; // Preview Manag
|
|||||||
const envelopeManager = window.envelopePreviewManager;
|
const envelopeManager = window.envelopePreviewManager;
|
||||||
if (envelopeManager && currentState.answers?.envelope === true) {
|
if (envelopeManager && currentState.answers?.envelope === true) {
|
||||||
try {
|
try {
|
||||||
envelopeManager.previewCount = parseInt(currentState.answers?.quantity) || 1;
|
envelopeManager.previewCount =
|
||||||
|
parseInt(currentState.answers?.quantity) || 1;
|
||||||
await envelopeManager.loadAllPreviews(currentState, true, true);
|
await envelopeManager.loadAllPreviews(currentState, true, true);
|
||||||
console.log('[App] Envelope previews generated for cache');
|
console.log("[App] Envelope previews generated for cache");
|
||||||
} catch (envError) {
|
} catch (envError) {
|
||||||
console.error('[App] Envelope preview generation failed:', envError);
|
console.error(
|
||||||
|
"[App] Envelope preview generation failed:",
|
||||||
|
envError,
|
||||||
|
);
|
||||||
// Nicht blockieren - Umschläge sind nicht kritisch für Navigation
|
// Nicht blockieren - Umschläge sind nicht kritisch für Navigation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,8 +161,11 @@ import PreviewManager from './configurator-preview-manager.js'; // Preview Manag
|
|||||||
hideValidationOverlay();
|
hideValidationOverlay();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
hideValidationOverlay();
|
hideValidationOverlay();
|
||||||
console.error('[App] Validation error:', error);
|
console.error("[App] Validation error:", error);
|
||||||
showValidationError(error.message || 'Validierung fehlgeschlagen', dom.form);
|
showValidationError(
|
||||||
|
error.message || "Validierung fehlgeschlagen",
|
||||||
|
dom.form,
|
||||||
|
);
|
||||||
return; // Nicht weiter navigieren
|
return; // Nicht weiter navigieren
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,21 +182,39 @@ import PreviewManager from './configurator-preview-manager.js'; // Preview Manag
|
|||||||
|
|
||||||
// Keyboard Navigation für Previews (nur innerhalb des aktuellen Batches)
|
// Keyboard Navigation für Previews (nur innerhalb des aktuellen Batches)
|
||||||
const keydownHandler = (e) => {
|
const keydownHandler = (e) => {
|
||||||
if (window.envelopePreviewManager && window.envelopePreviewManager.currentBatchPreviews.length > 0) {
|
if (
|
||||||
window.envelopePreviewManager.handleKeyboardNavigation(e, state, dom.preview, true);
|
window.envelopePreviewManager &&
|
||||||
|
window.envelopePreviewManager.currentBatchPreviews.length > 0
|
||||||
|
) {
|
||||||
|
window.envelopePreviewManager.handleKeyboardNavigation(
|
||||||
|
e,
|
||||||
|
state,
|
||||||
|
dom.preview,
|
||||||
|
true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (window.contentPreviewManager && window.contentPreviewManager.currentBatchPreviews.length > 0) {
|
if (
|
||||||
window.contentPreviewManager.handleKeyboardNavigation(e, state, dom.preview, false);
|
window.contentPreviewManager &&
|
||||||
|
window.contentPreviewManager.currentBatchPreviews.length > 0
|
||||||
|
) {
|
||||||
|
window.contentPreviewManager.handleKeyboardNavigation(
|
||||||
|
e,
|
||||||
|
state,
|
||||||
|
dom.preview,
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('keydown', keydownHandler);
|
document.addEventListener("keydown", keydownHandler);
|
||||||
cleanupHandlers.push(() => document.removeEventListener('keydown', keydownHandler));
|
cleanupHandlers.push(() =>
|
||||||
|
document.removeEventListener("keydown", keydownHandler),
|
||||||
|
);
|
||||||
|
|
||||||
render({ state, dom, dispatch });
|
render({ state, dom, dispatch });
|
||||||
|
|
||||||
// Konfigurator sichtbar machen nachdem erster Render abgeschlossen ist
|
// Konfigurator sichtbar machen nachdem erster Render abgeschlossen ist
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
root.classList.add('sk-ready');
|
root.classList.add("sk-ready");
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -49,8 +49,11 @@ function getProductDefinitions() {
|
|||||||
products[key] = {
|
products[key] = {
|
||||||
...baseConfig,
|
...baseConfig,
|
||||||
label: backendSettings.label || key,
|
label: backendSettings.label || key,
|
||||||
description: backendSettings.description || 'Professionelle handgeschriebene Korrespondenz',
|
description:
|
||||||
basePrice: parseFloat(backendSettings.base_price) || 2.50,
|
backendSettings.description ||
|
||||||
|
"Professionelle handgeschriebene Korrespondenz",
|
||||||
|
basePrice: parseFloat(backendSettings.base_price) || 2.5,
|
||||||
|
imageUrl: backendSettings.image_url || "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,21 +89,22 @@ export function deriveContextFromUrl(search) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Quantity aus URL
|
// Quantity aus URL
|
||||||
const quantityParam = q.get('quantity');
|
const quantityParam = q.get("quantity");
|
||||||
const quantity = quantityParam ? parseInt(quantityParam, 10) : null;
|
const quantity = quantityParam ? parseInt(quantityParam, 10) : null;
|
||||||
|
|
||||||
// Format aus URL (a4, a6h = A6 Hochformat, a6q = A6 Querformat)
|
// Format aus URL (a4, a6h = A6 Hochformat, a6q = A6 Querformat)
|
||||||
const formatParam = q.get('format')?.toLowerCase();
|
const formatParam = q.get("format")?.toLowerCase();
|
||||||
let format = null;
|
let format = null;
|
||||||
if (formatParam === 'a4') format = 'a4';
|
if (formatParam === "a4") format = "a4";
|
||||||
else if (formatParam === 'a6h') format = 'a6p'; // Hochformat
|
else if (formatParam === "a6h")
|
||||||
else if (formatParam === 'a6q') format = 'a6l'; // Querformat
|
format = "a6p"; // Hochformat
|
||||||
|
else if (formatParam === "a6q") format = "a6l"; // Querformat
|
||||||
|
|
||||||
// noPrice Parameter (Preise ausblenden)
|
// noPrice Parameter (Preise ausblenden)
|
||||||
const noPrice = q.has('noPrice') || q.has('noprice');
|
const noPrice = q.has("noPrice") || q.has("noprice");
|
||||||
|
|
||||||
// noLimits Parameter (keine Mindestmengen)
|
// noLimits Parameter (keine Mindestmengen)
|
||||||
const noLimits = q.has('noLimits') || q.has('nolimits');
|
const noLimits = q.has("noLimits") || q.has("nolimits");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
urlParam: urlParam || null,
|
urlParam: urlParam || null,
|
||||||
@@ -162,11 +166,14 @@ export function createInitialState(ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Standardmenge auf beste Preismenge (normalQuantity) setzen, außer URL-Parameter
|
// Standardmenge auf beste Preismenge (normalQuantity) setzen, außer URL-Parameter
|
||||||
const dynamicPricing = window.SkriftConfigurator?.settings?.dynamic_pricing || {};
|
const dynamicPricing =
|
||||||
|
window.SkriftConfigurator?.settings?.dynamic_pricing || {};
|
||||||
const isB2B = initialCustomerType === "business";
|
const isB2B = initialCustomerType === "business";
|
||||||
const defaultQuantity = urlQuantity || (isB2B
|
const defaultQuantity =
|
||||||
? (dynamicPricing.business_normal_quantity || 200)
|
urlQuantity ||
|
||||||
: (dynamicPricing.private_normal_quantity || 50));
|
(isB2B
|
||||||
|
? dynamicPricing.business_normal_quantity || 200
|
||||||
|
: dynamicPricing.private_normal_quantity || 50);
|
||||||
|
|
||||||
// Format aus URL oder null
|
// Format aus URL oder null
|
||||||
const initialFormat = urlFormat || null;
|
const initialFormat = urlFormat || null;
|
||||||
@@ -217,6 +224,8 @@ export function createInitialState(ctx) {
|
|||||||
// Inhalt
|
// Inhalt
|
||||||
contentCreateMode: null, // 'self' | 'textservice'
|
contentCreateMode: null, // 'self' | 'textservice'
|
||||||
letterText: "",
|
letterText: "",
|
||||||
|
font: "tilda",
|
||||||
|
envelopeFont: "tilda",
|
||||||
|
|
||||||
// Motiv
|
// Motiv
|
||||||
motifNeed: null,
|
motifNeed: null,
|
||||||
@@ -232,7 +241,7 @@ export function createInitialState(ctx) {
|
|||||||
|
|
||||||
recipientRows: [],
|
recipientRows: [],
|
||||||
// Adressmodus: 'classic' (Name, Anschrift) oder 'free' (5 freie Zeilen)
|
// Adressmodus: 'classic' (Name, Anschrift) oder 'free' (5 freie Zeilen)
|
||||||
addressMode: 'classic',
|
addressMode: "classic",
|
||||||
// Freie Adresszeilen (separat gespeichert, damit beim Wechsel nichts verloren geht)
|
// Freie Adresszeilen (separat gespeichert, damit beim Wechsel nichts verloren geht)
|
||||||
freeAddressRows: [],
|
freeAddressRows: [],
|
||||||
placeholders: {
|
placeholders: {
|
||||||
@@ -314,9 +323,8 @@ export function calcEffectiveEnvelopeType(state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getAvailableProductsForCustomerType(customerType) {
|
export function getAvailableProductsForCustomerType(customerType) {
|
||||||
return Object.values(PRODUCT_BY_PARAM).filter(
|
const fresh = getProductDefinitions();
|
||||||
(p) => p.category === customerType
|
return Object.values(fresh).filter((p) => p.category === customerType);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function syncPlaceholders(state) {
|
export function syncPlaceholders(state) {
|
||||||
@@ -406,109 +414,17 @@ export function validateStep(state) {
|
|||||||
// Versandart muss gewählt sein
|
// Versandart muss gewählt sein
|
||||||
if (!state.answers.shippingMode) return false;
|
if (!state.answers.shippingMode) return false;
|
||||||
|
|
||||||
// Bei Versand durch Skrift ist Umschlag automatisch
|
if (state.answers.shippingMode !== "direct") {
|
||||||
if (state.answers.shippingMode === "direct") {
|
|
||||||
// Automatisch gesetzt, keine Validierung nötig für envelope
|
|
||||||
if (!state.answers.envelopeMode) return false;
|
|
||||||
|
|
||||||
// Bei Follow-ups: keine Empfängerdaten-Validierung (kommt aus CRM)
|
|
||||||
// Bei regulären Produkten: Empfängerdaten müssen vorhanden sein
|
|
||||||
if (!isFollowups(state)) {
|
|
||||||
// Empfängerdaten validieren für reguläre Produkte
|
|
||||||
if (state.answers.envelopeMode === "recipientData") {
|
|
||||||
const addressMode = state.addressMode || 'classic';
|
|
||||||
|
|
||||||
if (addressMode === 'free') {
|
|
||||||
// Freie Adresse: mindestens Zeile 1 muss ausgefüllt sein
|
|
||||||
const rows = state.freeAddressRows || [];
|
|
||||||
if (rows.length !== requiredRowCount(state)) return false;
|
|
||||||
|
|
||||||
for (const r of rows) {
|
|
||||||
if (!r) return false;
|
|
||||||
// Mindestens Zeile 1 muss ausgefüllt sein
|
|
||||||
if (!String(r.line1 || "").trim()) return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Klassische Adresse
|
|
||||||
const rows = state.recipientRows || [];
|
|
||||||
if (rows.length !== requiredRowCount(state)) return false;
|
|
||||||
|
|
||||||
for (const r of rows) {
|
|
||||||
if (!r) return false;
|
|
||||||
const required = [
|
|
||||||
"firstName",
|
|
||||||
"lastName",
|
|
||||||
"street",
|
|
||||||
"houseNumber",
|
|
||||||
"zip",
|
|
||||||
"city",
|
|
||||||
"country",
|
|
||||||
];
|
|
||||||
for (const k of required) {
|
|
||||||
if (!String(r[k] || "").trim()) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Bulk versand
|
// Bulk versand
|
||||||
if (state.answers.envelope === null) return false;
|
if (state.answers.envelope === null) return false;
|
||||||
|
|
||||||
if (state.answers.envelope === true) {
|
if (state.answers.envelope === true) {
|
||||||
// Beschriftungsmodus muss gewählt sein
|
|
||||||
if (!state.answers.envelopeMode) return false;
|
if (!state.answers.envelopeMode) return false;
|
||||||
|
|
||||||
// Empfängerdaten validieren für Bulk-Versand (nur wenn Umschlag gewählt)
|
|
||||||
if (state.answers.envelopeMode === "recipientData") {
|
|
||||||
const addressMode = state.addressMode || 'classic';
|
|
||||||
|
|
||||||
if (addressMode === 'free') {
|
|
||||||
// Freie Adresse: mindestens Zeile 1 muss ausgefüllt sein
|
|
||||||
const rows = state.freeAddressRows || [];
|
|
||||||
if (rows.length !== requiredRowCount(state)) return false;
|
|
||||||
|
|
||||||
for (const r of rows) {
|
|
||||||
if (!r) return false;
|
|
||||||
if (!String(r.line1 || "").trim()) return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Klassische Adresse
|
|
||||||
const rows = state.recipientRows || [];
|
|
||||||
if (rows.length !== requiredRowCount(state)) return false;
|
|
||||||
|
|
||||||
for (const r of rows) {
|
|
||||||
if (!r) return false;
|
|
||||||
const required = [
|
|
||||||
"firstName",
|
|
||||||
"lastName",
|
|
||||||
"street",
|
|
||||||
"houseNumber",
|
|
||||||
"zip",
|
|
||||||
"city",
|
|
||||||
"country",
|
|
||||||
];
|
|
||||||
for (const k of required) {
|
|
||||||
if (!String(r[k] || "").trim()) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom Text validieren (nur wenn Umschlag gewählt)
|
|
||||||
if (state.answers.envelopeMode === "customText") {
|
if (state.answers.envelopeMode === "customText") {
|
||||||
if (!state.answers.envelopeCustomText?.trim()) return false;
|
if (!state.answers.envelopeCustomText?.trim()) return false;
|
||||||
|
|
||||||
if (state.placeholders.envelope.length > 0) {
|
|
||||||
for (const ph of state.placeholders.envelope) {
|
|
||||||
const arr = state.placeholderValues[ph] || [];
|
|
||||||
if (arr.length !== requiredRowCount(state)) return false;
|
|
||||||
if (arr.some((v) => !String(v || "").trim())) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Wenn envelope === false, keine weitere Validierung nötig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -520,31 +436,6 @@ export function validateStep(state) {
|
|||||||
// Wenn self, muss Text vorhanden sein
|
// Wenn self, muss Text vorhanden sein
|
||||||
if (state.answers.contentCreateMode === "self") {
|
if (state.answers.contentCreateMode === "self") {
|
||||||
if (!state.answers.letterText?.trim()) return false;
|
if (!state.answers.letterText?.trim()) return false;
|
||||||
|
|
||||||
// Platzhalter validieren
|
|
||||||
const usedLetter = extractPlaceholders(state.answers.letterText);
|
|
||||||
|
|
||||||
// Platzhalter validieren
|
|
||||||
// "vorname", "name", "ort" können immer aus recipientRows kommen (bei shippingMode=direct oder envelopeMode=recipientData)
|
|
||||||
const recipientPlaceholders = new Set(["vorname", "name", "ort"]);
|
|
||||||
const hasRecipientData = state.answers.shippingMode === "direct" || state.answers.envelopeMode === "recipientData";
|
|
||||||
|
|
||||||
// Platzhalter aus dem Umschlag
|
|
||||||
const envSet = new Set(state.placeholders.envelope || []);
|
|
||||||
|
|
||||||
// Platzhalter die validiert werden müssen (nicht aus Umschlag, nicht aus recipientRows)
|
|
||||||
const needed = usedLetter.filter((p) => {
|
|
||||||
if (envSet.has(p)) return false; // aus Umschlag
|
|
||||||
if (hasRecipientData && recipientPlaceholders.has(p)) return false; // aus recipientRows
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Nur die übrigen Platzhalter validieren
|
|
||||||
for (const ph of needed) {
|
|
||||||
const arr = state.placeholderValues[ph] || [];
|
|
||||||
if (arr.length !== requiredRowCount(state)) return false;
|
|
||||||
if (arr.some((v) => !String(v || "").trim())) return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Motiv Validierung
|
// Motiv Validierung
|
||||||
@@ -594,7 +485,14 @@ export function validateStep(state) {
|
|||||||
if (state.order.shippingDifferent) {
|
if (state.order.shippingDifferent) {
|
||||||
const sh = state.order.shipping;
|
const sh = state.order.shipping;
|
||||||
// Shipping braucht kein E-Mail/Telefon - nur Adressfelder
|
// Shipping braucht kein E-Mail/Telefon - nur Adressfelder
|
||||||
const shippingReq = ["firstName", "lastName", "street", "zip", "city", "country"];
|
const shippingReq = [
|
||||||
|
"firstName",
|
||||||
|
"lastName",
|
||||||
|
"street",
|
||||||
|
"zip",
|
||||||
|
"city",
|
||||||
|
"country",
|
||||||
|
];
|
||||||
for (const k of shippingReq) {
|
for (const k of shippingReq) {
|
||||||
if (!String(sh[k] || "").trim()) return false;
|
if (!String(sh[k] || "").trim()) return false;
|
||||||
}
|
}
|
||||||
@@ -614,17 +512,17 @@ export function validateStep(state) {
|
|||||||
|
|
||||||
// Preisrelevante Felder die eine Neuberechnung auslösen
|
// Preisrelevante Felder die eine Neuberechnung auslösen
|
||||||
const PRICE_RELEVANT_FIELDS = new Set([
|
const PRICE_RELEVANT_FIELDS = new Set([
|
||||||
'quantity',
|
"quantity",
|
||||||
'format',
|
"format",
|
||||||
'shippingMode',
|
"shippingMode",
|
||||||
'envelope',
|
"envelope",
|
||||||
'envelopeMode',
|
"envelopeMode",
|
||||||
'motifSource',
|
"motifSource",
|
||||||
'motifNeed',
|
"motifNeed",
|
||||||
'contentCreateMode',
|
"contentCreateMode",
|
||||||
'followupYearlyVolume',
|
"followupYearlyVolume",
|
||||||
'followupCreateMode',
|
"followupCreateMode",
|
||||||
'customerType',
|
"customerType",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -632,7 +530,7 @@ const PRICE_RELEVANT_FIELDS = new Set([
|
|||||||
*/
|
*/
|
||||||
function hasPriceRelevantChanges(patch) {
|
function hasPriceRelevantChanges(patch) {
|
||||||
if (!patch) return false;
|
if (!patch) return false;
|
||||||
return Object.keys(patch).some(key => PRICE_RELEVANT_FIELDS.has(key));
|
return Object.keys(patch).some((key) => PRICE_RELEVANT_FIELDS.has(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -647,19 +545,20 @@ function countCountryDistribution(rows, addressMode) {
|
|||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
if (!row) continue;
|
if (!row) continue;
|
||||||
|
|
||||||
let country = '';
|
let country = "";
|
||||||
if (addressMode === 'free') {
|
if (addressMode === "free") {
|
||||||
country = row.line5 || '';
|
country = row.line5 || "";
|
||||||
} else {
|
} else {
|
||||||
country = row.country || '';
|
country = row.country || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const countryLower = country.toLowerCase().trim();
|
const countryLower = country.toLowerCase().trim();
|
||||||
const isDomestic = !countryLower ||
|
const isDomestic =
|
||||||
countryLower === 'deutschland' ||
|
!countryLower ||
|
||||||
countryLower === 'germany' ||
|
countryLower === "deutschland" ||
|
||||||
countryLower === 'de' ||
|
countryLower === "germany" ||
|
||||||
countryLower === 'ger';
|
countryLower === "de" ||
|
||||||
|
countryLower === "ger";
|
||||||
|
|
||||||
if (isDomestic) {
|
if (isDomestic) {
|
||||||
domestic++;
|
domestic++;
|
||||||
@@ -710,6 +609,11 @@ export function reducer(state, action) {
|
|||||||
if (p.urlFormat !== null) delete answerPatch.format;
|
if (p.urlFormat !== null) delete answerPatch.format;
|
||||||
next.answers = { ...next.answers, ...answerPatch };
|
next.answers = { ...next.answers, ...answerPatch };
|
||||||
|
|
||||||
|
// Auto-Logik: Bei Direktversand immer envelopeMode sicherstellen
|
||||||
|
if (next.answers.shippingMode === "direct" && !next.answers.envelopeMode) {
|
||||||
|
next.answers = { ...next.answers, envelope: true, envelopeMode: "recipientData" };
|
||||||
|
}
|
||||||
|
|
||||||
// Empfängerdaten
|
// Empfängerdaten
|
||||||
if (Array.isArray(p.recipientRows)) {
|
if (Array.isArray(p.recipientRows)) {
|
||||||
next.recipientRows = p.recipientRows;
|
next.recipientRows = p.recipientRows;
|
||||||
@@ -737,7 +641,11 @@ export function reducer(state, action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Step wiederherstellen (nur wenn nicht im PRODUCT Step)
|
// Step wiederherstellen (nur wenn nicht im PRODUCT Step)
|
||||||
if (typeof p.step === "number" && p.step >= 0 && p.currentStep !== STEPS.PRODUCT) {
|
if (
|
||||||
|
typeof p.step === "number" &&
|
||||||
|
p.step >= 0 &&
|
||||||
|
p.currentStep !== STEPS.PRODUCT
|
||||||
|
) {
|
||||||
next.step = p.step;
|
next.step = p.step;
|
||||||
next.history = [];
|
next.history = [];
|
||||||
for (let i = 0; i < p.step; i++) {
|
for (let i = 0; i < p.step; i++) {
|
||||||
@@ -745,6 +653,12 @@ export function reducer(state, action) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wenn nur ein Format verfügbar und keins gespeichert: automatisch setzen
|
||||||
|
const hydrateFormats = next.ctx?.product?.formats || [];
|
||||||
|
if (hydrateFormats.length === 1 && !next.answers.format) {
|
||||||
|
next.answers = { ...next.answers, format: hydrateFormats[0] };
|
||||||
|
}
|
||||||
|
|
||||||
// Quote einmal am Ende berechnen
|
// Quote einmal am Ende berechnen
|
||||||
next = recalculateQuote(next);
|
next = recalculateQuote(next);
|
||||||
|
|
||||||
@@ -773,6 +687,11 @@ export function reducer(state, action) {
|
|||||||
next.answers = { ...next.answers, format: null };
|
next.answers = { ...next.answers, format: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wenn nur ein Format verfügbar: automatisch setzen
|
||||||
|
const availFormats = action.product?.formats || [];
|
||||||
|
if (availFormats.length === 1 && !next.answers.format) {
|
||||||
|
next.answers = { ...next.answers, format: availFormats[0] };
|
||||||
|
}
|
||||||
// Follow-ups: Automatisch Direktversand setzen
|
// Follow-ups: Automatisch Direktversand setzen
|
||||||
if (action.product?.key === "follow-ups") {
|
if (action.product?.key === "follow-ups") {
|
||||||
next.answers = { ...next.answers, shippingMode: "direct" };
|
next.answers = { ...next.answers, shippingMode: "direct" };
|
||||||
@@ -796,6 +715,11 @@ export function reducer(state, action) {
|
|||||||
next.answers = { ...next.answers, format: null };
|
next.answers = { ...next.answers, format: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wenn nur ein Format verfügbar: automatisch setzen
|
||||||
|
const availFormats = action.product?.formats || [];
|
||||||
|
if (availFormats.length === 1 && !next.answers.format) {
|
||||||
|
next.answers = { ...next.answers, format: availFormats[0] };
|
||||||
|
}
|
||||||
// Follow-ups: Automatisch Direktversand setzen
|
// Follow-ups: Automatisch Direktversand setzen
|
||||||
if (action.product?.key === "follow-ups") {
|
if (action.product?.key === "follow-ups") {
|
||||||
next.answers = { ...next.answers, shippingMode: "direct" };
|
next.answers = { ...next.answers, shippingMode: "direct" };
|
||||||
@@ -813,7 +737,7 @@ export function reducer(state, action) {
|
|||||||
|
|
||||||
// WICHTIG: Preview Cache löschen wenn envelopeMode geändert wird
|
// WICHTIG: Preview Cache löschen wenn envelopeMode geändert wird
|
||||||
if (action.patch && "envelopeMode" in action.patch) {
|
if (action.patch && "envelopeMode" in action.patch) {
|
||||||
console.log('[State] envelopeMode changed, clearing envelope previews');
|
console.log("[State] envelopeMode changed, clearing envelope previews");
|
||||||
if (window.envelopePreviewManager) {
|
if (window.envelopePreviewManager) {
|
||||||
window.envelopePreviewManager.currentBatchPreviews = [];
|
window.envelopePreviewManager.currentBatchPreviews = [];
|
||||||
window.envelopePreviewManager.currentDocIndex = 0;
|
window.envelopePreviewManager.currentDocIndex = 0;
|
||||||
@@ -834,10 +758,13 @@ export function reducer(state, action) {
|
|||||||
};
|
};
|
||||||
// Bei Einzelversand: Leere Länder-Felder auf "Deutschland" setzen (Default)
|
// Bei Einzelversand: Leere Länder-Felder auf "Deutschland" setzen (Default)
|
||||||
if (Array.isArray(next.recipientRows)) {
|
if (Array.isArray(next.recipientRows)) {
|
||||||
next.recipientRows = next.recipientRows.map(row => ({
|
next.recipientRows = next.recipientRows.map((row) => ({
|
||||||
...row,
|
...row,
|
||||||
// Nur setzen wenn Feld leer ist
|
// Nur setzen wenn Feld leer ist
|
||||||
country: row.country && row.country.trim() !== "" ? row.country : "Deutschland"
|
country:
|
||||||
|
row.country && row.country.trim() !== ""
|
||||||
|
? row.country
|
||||||
|
: "Deutschland",
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -848,20 +775,21 @@ export function reducer(state, action) {
|
|||||||
// Nur setzen wenn Menge noch nicht vom Benutzer geändert wurde
|
// Nur setzen wenn Menge noch nicht vom Benutzer geändert wurde
|
||||||
// (d.h. wenn sie noch dem alten Default entspricht oder nicht gesetzt ist)
|
// (d.h. wenn sie noch dem alten Default entspricht oder nicht gesetzt ist)
|
||||||
const currentQty = next.answers.quantity;
|
const currentQty = next.answers.quantity;
|
||||||
const dynamicPricing = window.SkriftConfigurator?.settings?.dynamic_pricing || {};
|
const dynamicPricing =
|
||||||
|
window.SkriftConfigurator?.settings?.dynamic_pricing || {};
|
||||||
|
|
||||||
// Alte Default-Werte berechnen
|
// Alte Default-Werte berechnen
|
||||||
const wasB2B = state.answers.customerType === "business";
|
const wasB2B = state.answers.customerType === "business";
|
||||||
const oldDefaultQty = wasB2B
|
const oldDefaultQty = wasB2B
|
||||||
? (dynamicPricing.business_normal_quantity || 200)
|
? dynamicPricing.business_normal_quantity || 200
|
||||||
: (dynamicPricing.private_normal_quantity || 50);
|
: dynamicPricing.private_normal_quantity || 50;
|
||||||
|
|
||||||
// Nur überschreiben wenn Menge noch dem alten Default entspricht oder nicht gesetzt ist
|
// Nur überschreiben wenn Menge noch dem alten Default entspricht oder nicht gesetzt ist
|
||||||
if (!currentQty || currentQty === oldDefaultQty || currentQty === 1) {
|
if (!currentQty || currentQty === oldDefaultQty || currentQty === 1) {
|
||||||
const isB2B = action.patch.customerType === "business";
|
const isB2B = action.patch.customerType === "business";
|
||||||
const newDefaultQuantity = isB2B
|
const newDefaultQuantity = isB2B
|
||||||
? (dynamicPricing.business_normal_quantity || 200)
|
? dynamicPricing.business_normal_quantity || 200
|
||||||
: (dynamicPricing.private_normal_quantity || 50);
|
: dynamicPricing.private_normal_quantity || 50;
|
||||||
next.answers = { ...next.answers, quantity: newDefaultQuantity };
|
next.answers = { ...next.answers, quantity: newDefaultQuantity };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -885,7 +813,11 @@ export function reducer(state, action) {
|
|||||||
// Auto-Logik für motifNeed: Wenn kein Motiv gewählt, motifSource zurücksetzen
|
// Auto-Logik für motifNeed: Wenn kein Motiv gewählt, motifSource zurücksetzen
|
||||||
if (action.patch && "motifNeed" in action.patch) {
|
if (action.patch && "motifNeed" in action.patch) {
|
||||||
if (action.patch.motifNeed === false) {
|
if (action.patch.motifNeed === false) {
|
||||||
next.answers = { ...next.answers, motifSource: null, serviceDesign: false };
|
next.answers = {
|
||||||
|
...next.answers,
|
||||||
|
motifSource: null,
|
||||||
|
serviceDesign: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -893,7 +825,10 @@ export function reducer(state, action) {
|
|||||||
if (action.patch && "format" in action.patch) {
|
if (action.patch && "format" in action.patch) {
|
||||||
if (action.patch.format === "a4") {
|
if (action.patch.format === "a4") {
|
||||||
// Wenn upload oder design ausgewählt war, auf printed umstellen
|
// Wenn upload oder design ausgewählt war, auf printed umstellen
|
||||||
if (next.answers.motifSource === "upload" || next.answers.motifSource === "design") {
|
if (
|
||||||
|
next.answers.motifSource === "upload" ||
|
||||||
|
next.answers.motifSource === "design"
|
||||||
|
) {
|
||||||
next.answers = { ...next.answers, motifSource: "printed" };
|
next.answers = { ...next.answers, motifSource: "printed" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -914,9 +849,18 @@ export function reducer(state, action) {
|
|||||||
// Quote nur neu berechnen wenn sich Inland/Ausland-Verteilung geändert hat
|
// Quote nur neu berechnen wenn sich Inland/Ausland-Verteilung geändert hat
|
||||||
let next = { ...state, recipientRows: action.rows };
|
let next = { ...state, recipientRows: action.rows };
|
||||||
if (state.answers?.shippingMode === "direct") {
|
if (state.answers?.shippingMode === "direct") {
|
||||||
const oldDist = countCountryDistribution(state.recipientRows, state.addressMode);
|
const oldDist = countCountryDistribution(
|
||||||
const newDist = countCountryDistribution(action.rows, state.addressMode);
|
state.recipientRows,
|
||||||
if (oldDist.domestic !== newDist.domestic || oldDist.international !== newDist.international) {
|
state.addressMode,
|
||||||
|
);
|
||||||
|
const newDist = countCountryDistribution(
|
||||||
|
action.rows,
|
||||||
|
state.addressMode,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
oldDist.domestic !== newDist.domestic ||
|
||||||
|
oldDist.international !== newDist.international
|
||||||
|
) {
|
||||||
next = recalculateQuote(next);
|
next = recalculateQuote(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -936,9 +880,12 @@ export function reducer(state, action) {
|
|||||||
// Quote nur neu berechnen wenn sich Inland/Ausland-Verteilung geändert hat
|
// Quote nur neu berechnen wenn sich Inland/Ausland-Verteilung geändert hat
|
||||||
let next = { ...state, freeAddressRows: action.rows };
|
let next = { ...state, freeAddressRows: action.rows };
|
||||||
if (state.answers?.shippingMode === "direct") {
|
if (state.answers?.shippingMode === "direct") {
|
||||||
const oldDist = countCountryDistribution(state.freeAddressRows, 'free');
|
const oldDist = countCountryDistribution(state.freeAddressRows, "free");
|
||||||
const newDist = countCountryDistribution(action.rows, 'free');
|
const newDist = countCountryDistribution(action.rows, "free");
|
||||||
if (oldDist.domestic !== newDist.domestic || oldDist.international !== newDist.international) {
|
if (
|
||||||
|
oldDist.domestic !== newDist.domestic ||
|
||||||
|
oldDist.international !== newDist.international
|
||||||
|
) {
|
||||||
next = recalculateQuote(next);
|
next = recalculateQuote(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -967,8 +914,35 @@ export function reducer(state, action) {
|
|||||||
return { ...state, placeholderValues: action.values || {} };
|
return { ...state, placeholderValues: action.values || {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "SYNC_GLOBAL_STATE": {
|
||||||
|
// Synchronisiert alle direkt mutierten Daten (aus window.currentGlobalState)
|
||||||
|
// in einem einzigen Dispatch ohne mehrfaches Re-Render
|
||||||
|
const p = action.payload || {};
|
||||||
|
let next = { ...state };
|
||||||
|
|
||||||
|
if (Array.isArray(p.recipientRows)) {
|
||||||
|
next.recipientRows = p.recipientRows;
|
||||||
|
}
|
||||||
|
if (Array.isArray(p.freeAddressRows)) {
|
||||||
|
next.freeAddressRows = p.freeAddressRows;
|
||||||
|
}
|
||||||
|
if (p.placeholderValues && typeof p.placeholderValues === "object") {
|
||||||
|
next.placeholderValues = p.placeholderValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quote neu berechnen falls relevant
|
||||||
|
if (next.answers?.shippingMode === "direct") {
|
||||||
|
next = recalculateQuote(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
case "SET_ORDER": {
|
case "SET_ORDER": {
|
||||||
const nextState = { ...state, order: { ...state.order, ...action.patch } };
|
const nextState = {
|
||||||
|
...state,
|
||||||
|
order: { ...state.order, ...action.patch },
|
||||||
|
};
|
||||||
// Quote neu berechnen wenn Gutschein geändert wurde
|
// Quote neu berechnen wenn Gutschein geändert wurde
|
||||||
return recalculateQuote(nextState);
|
return recalculateQuote(nextState);
|
||||||
}
|
}
|
||||||
@@ -1000,7 +974,7 @@ export function reducer(state, action) {
|
|||||||
const nextStep = Math.min(STEPS.REVIEW, state.step + 1);
|
const nextStep = Math.min(STEPS.REVIEW, state.step + 1);
|
||||||
|
|
||||||
// Nach oben scrollen
|
// Nach oben scrollen
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@@ -1015,7 +989,7 @@ export function reducer(state, action) {
|
|||||||
const prev = hist[hist.length - 1];
|
const prev = hist[hist.length - 1];
|
||||||
|
|
||||||
// Nach oben scrollen
|
// Nach oben scrollen
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
|
|
||||||
return { ...state, step: prev, history: hist.slice(0, -1) };
|
return { ...state, step: prev, history: hist.slice(0, -1) };
|
||||||
}
|
}
|
||||||
@@ -1026,5 +1000,7 @@ export function reducer(state, action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function listProducts() {
|
export function listProducts() {
|
||||||
return Object.values(PRODUCT_BY_PARAM);
|
// Immer frisch laden damit Backend-Settings (inkl. imageUrl) verfügbar sind
|
||||||
|
const fresh = getProductDefinitions();
|
||||||
|
return Object.values(fresh);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
normalizePlaceholderName,
|
normalizePlaceholderName,
|
||||||
validateStep,
|
validateStep,
|
||||||
getAvailableProductsForCustomerType,
|
getAvailableProductsForCustomerType,
|
||||||
} from "./configurator-state.js?ver=0.3.0";
|
} from "./configurator-state.js?ver=0.3.1";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
calculatePricePerPiece,
|
calculatePricePerPiece,
|
||||||
@@ -725,7 +725,16 @@ function renderTopbar(dom, state) {
|
|||||||
|
|
||||||
if (state?.ctx?.product) {
|
if (state?.ctx?.product) {
|
||||||
productInfo.appendChild(
|
productInfo.appendChild(
|
||||||
h("div", { class: "sk-product-icon", text: state.ctx.product.label[0] }),
|
state.ctx.product.imageUrl
|
||||||
|
? h("img", {
|
||||||
|
src: state.ctx.product.imageUrl,
|
||||||
|
alt: state.ctx.product.label,
|
||||||
|
class: "sk-product-icon sk-product-icon-img",
|
||||||
|
})
|
||||||
|
: h("div", {
|
||||||
|
class: "sk-product-icon",
|
||||||
|
text: state.ctx.product.label[0],
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
productInfo.appendChild(
|
productInfo.appendChild(
|
||||||
h("div", {}, [
|
h("div", {}, [
|
||||||
@@ -835,7 +844,13 @@ function renderProductStep(state, dispatch) {
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
h("div", { class: "sk-selection-card-image" }, [
|
h("div", { class: "sk-selection-card-image" }, [
|
||||||
h("div", { text: "📄", style: "font-size: 48px" }), // Placeholder
|
p.imageUrl
|
||||||
|
? h("img", {
|
||||||
|
src: p.imageUrl,
|
||||||
|
alt: p.label,
|
||||||
|
class: "sk-selection-card-img",
|
||||||
|
})
|
||||||
|
: h("div", { text: "📄", style: "font-size: 48px" }),
|
||||||
]),
|
]),
|
||||||
h("div", { class: "sk-selection-card-content" }, [
|
h("div", { class: "sk-selection-card-content" }, [
|
||||||
h("div", { class: "sk-selection-card-title", text: p.label }),
|
h("div", { class: "sk-selection-card-title", text: p.label }),
|
||||||
@@ -1059,20 +1074,6 @@ function renderQuantityStep(state, dispatch) {
|
|||||||
h("div", { class: "sk-options" }, formatBody),
|
h("div", { class: "sk-options" }, formatBody),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
} else if (formats.length === 1) {
|
|
||||||
// Auto-set single format (nur wenn noch nicht gesetzt)
|
|
||||||
if (!state.answers.format) {
|
|
||||||
dispatch({ type: "ANSWER", patch: { format: formats[0] } });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schriftart wird jetzt in Umschlag und Inhalt Steps separat gewählt (nicht hier)
|
|
||||||
// Default auf tilda wenn noch nicht gesetzt
|
|
||||||
if (!state.answers.font) {
|
|
||||||
dispatch({ type: "ANSWER", patch: { font: "tilda" } });
|
|
||||||
}
|
|
||||||
if (!state.answers.envelopeFont) {
|
|
||||||
dispatch({ type: "ANSWER", patch: { envelopeFont: "tilda" } });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Follow-ups: Erstellungsmodus
|
// Follow-ups: Erstellungsmodus
|
||||||
@@ -2223,6 +2224,9 @@ async function handleOrderSubmit(state) {
|
|||||||
finalizeResult = await api.finalizeOrder(api.sessionId, orderNumber, {
|
finalizeResult = await api.finalizeOrder(api.sessionId, orderNumber, {
|
||||||
customer: state.order,
|
customer: state.order,
|
||||||
quote: state.quote,
|
quote: state.quote,
|
||||||
|
letterFormat: { a4: "A4", a6p: "A6 Hochformat", a6l: "A6 Querformat" }[state.answers.format] || state.answers.format || null,
|
||||||
|
envelopeFormat: state.answers.format === "a4" ? "DIN Lang" : (state.answers.format === "a6p" || state.answers.format === "a6l") ? "C6" : null,
|
||||||
|
envelopeBeschriftung: { recipientData: "Empfängeradresse", customText: "Individueller Text", none: "Keine Beschriftung" }[state.answers.envelopeMode] || state.answers.envelopeMode || null,
|
||||||
});
|
});
|
||||||
console.log("[Order] Finalize result:", finalizeResult);
|
console.log("[Order] Finalize result:", finalizeResult);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -3356,6 +3360,9 @@ async function handlePayPalSuccess(state, paypalDetails) {
|
|||||||
status: paypalDetails.status,
|
status: paypalDetails.status,
|
||||||
},
|
},
|
||||||
quote: state.quote,
|
quote: state.quote,
|
||||||
|
letterFormat: { a4: "A4", a6p: "A6 Hochformat", a6l: "A6 Querformat" }[state.answers.format] || state.answers.format || null,
|
||||||
|
envelopeFormat: state.answers.format === "a4" ? "DIN Lang" : (state.answers.format === "a6p" || state.answers.format === "a6l") ? "C6" : null,
|
||||||
|
envelopeBeschriftung: { recipientData: "Empfängeradresse", customText: "Individueller Text", none: "Keine Beschriftung" }[state.answers.envelopeMode] || state.answers.envelopeMode || null,
|
||||||
});
|
});
|
||||||
console.log("[PayPal] Finalize result:", finalizeResult);
|
console.log("[PayPal] Finalize result:", finalizeResult);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -3671,6 +3678,7 @@ function createTableModal(dispatch, config) {
|
|||||||
newData.push({});
|
newData.push({});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Werte in lokale Daten UND direkt ins DOM schreiben (kein Re-Render)
|
||||||
let pastedCells = 0;
|
let pastedCells = 0;
|
||||||
rows.forEach((rowText, rowOffset) => {
|
rows.forEach((rowText, rowOffset) => {
|
||||||
const cells = rowText.split("\t");
|
const cells = rowText.split("\t");
|
||||||
@@ -3686,23 +3694,30 @@ function createTableModal(dispatch, config) {
|
|||||||
if (!col.readOnly) {
|
if (!col.readOnly) {
|
||||||
if (!newData[targetRow]) newData[targetRow] = {};
|
if (!newData[targetRow]) newData[targetRow] = {};
|
||||||
newData[targetRow][col.key] = cellValue.trim();
|
newData[targetRow][col.key] = cellValue.trim();
|
||||||
|
|
||||||
|
// DOM direkt aktualisieren (kein Re-Render nötig)
|
||||||
|
const input = table.querySelector(
|
||||||
|
`input[data-row="${targetRow}"][data-key="${col.key}"]`,
|
||||||
|
);
|
||||||
|
if (input) input.value = cellValue.trim();
|
||||||
|
|
||||||
pastedCells++;
|
pastedCells++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Daten direkt in globalen State schreiben (OHNE Re-Render)
|
||||||
if (pastedCells > 0) {
|
if (pastedCells > 0) {
|
||||||
config.setData(dispatch, newData);
|
const globalState = window.currentGlobalState;
|
||||||
|
if (globalState && config.stateKey) {
|
||||||
// Tabelle im Modal aktualisieren
|
globalState[config.stateKey] = newData;
|
||||||
renderTable();
|
}
|
||||||
|
markPersistDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flag nach kurzem Delay zurücksetzen (für evtl. verzögerte input-Events)
|
// Flag zurücksetzen
|
||||||
setTimeout(() => {
|
isPasting = false;
|
||||||
isPasting = false;
|
|
||||||
}, 50);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return table;
|
return table;
|
||||||
@@ -3933,7 +3948,9 @@ function createTableModal(dispatch, config) {
|
|||||||
{
|
{
|
||||||
class: "sk-modal",
|
class: "sk-modal",
|
||||||
onclick: (e) => {
|
onclick: (e) => {
|
||||||
if (e.target.classList.contains("sk-modal")) modal.remove();
|
if (e.target.classList.contains("sk-modal")) {
|
||||||
|
modal.remove();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[modalContent],
|
[modalContent],
|
||||||
@@ -4013,6 +4030,12 @@ function renderRecipientsTable(state, dispatch) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
currentRows[idx][key] = e.target.value;
|
currentRows[idx][key] = e.target.value;
|
||||||
|
|
||||||
|
// Auch globalen State aktualisieren für SYNC_GLOBAL_STATE
|
||||||
|
const globalState = window.currentGlobalState;
|
||||||
|
if (globalState) {
|
||||||
|
globalState.recipientRows = currentRows;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
@@ -4040,6 +4063,12 @@ function renderRecipientsTable(state, dispatch) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
currentRows[idx].country = e.target.value;
|
currentRows[idx].country = e.target.value;
|
||||||
|
|
||||||
|
// Auch globalen State aktualisieren für SYNC_GLOBAL_STATE
|
||||||
|
const globalState = window.currentGlobalState;
|
||||||
|
if (globalState) {
|
||||||
|
globalState.recipientRows = currentRows;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
@@ -4085,7 +4114,7 @@ function renderRecipientsTable(state, dispatch) {
|
|||||||
hasLocalChanges = true;
|
hasLocalChanges = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Paste-Handler
|
// Paste-Handler - Daten direkt in globalen State schreiben (OHNE Re-Render)
|
||||||
table.addEventListener("paste", (e) => {
|
table.addEventListener("paste", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const text = e.clipboardData.getData("text/plain");
|
const text = e.clipboardData.getData("text/plain");
|
||||||
@@ -4121,25 +4150,30 @@ function renderRecipientsTable(state, dispatch) {
|
|||||||
"city",
|
"city",
|
||||||
"country",
|
"country",
|
||||||
];
|
];
|
||||||
const currentRows = table._rows;
|
|
||||||
const newData = [];
|
// Aktuellen globalen State holen
|
||||||
for (let i = 0; i < want; i++) {
|
const globalState = window.currentGlobalState;
|
||||||
newData.push(
|
if (!globalState) return;
|
||||||
currentRows[i]
|
|
||||||
? { ...currentRows[i] }
|
// Kopie der aktuellen Rows erstellen
|
||||||
: {
|
const currentRows = Array.isArray(globalState.recipientRows)
|
||||||
firstName: "",
|
? globalState.recipientRows.map((r) => ({ ...r }))
|
||||||
lastName: "",
|
: [];
|
||||||
street: "",
|
|
||||||
houseNumber: "",
|
// Sicherstellen dass genug Zeilen existieren
|
||||||
zip: "",
|
while (currentRows.length < want) {
|
||||||
city: "",
|
currentRows.push({
|
||||||
country: isDirectShipping ? "Deutschland" : "",
|
firstName: "",
|
||||||
},
|
lastName: "",
|
||||||
);
|
street: "",
|
||||||
|
houseNumber: "",
|
||||||
|
zip: "",
|
||||||
|
city: "",
|
||||||
|
country: isDirectShipping ? "Deutschland" : "",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let pastedCells = 0;
|
// Werte in Daten UND direkt ins DOM schreiben
|
||||||
pasteRows.forEach((rowText, rowOffset) => {
|
pasteRows.forEach((rowText, rowOffset) => {
|
||||||
const targetRow = startRow + rowOffset;
|
const targetRow = startRow + rowOffset;
|
||||||
if (targetRow >= want) return;
|
if (targetRow >= want) return;
|
||||||
@@ -4149,15 +4183,35 @@ function renderRecipientsTable(state, dispatch) {
|
|||||||
const targetCol = startCol + colOffset;
|
const targetCol = startCol + colOffset;
|
||||||
if (targetCol >= keys.length) return;
|
if (targetCol >= keys.length) return;
|
||||||
const key = keys[targetCol];
|
const key = keys[targetCol];
|
||||||
newData[targetRow][key] = cellValue;
|
|
||||||
pastedCells++;
|
// Daten aktualisieren
|
||||||
|
if (!currentRows[targetRow]) {
|
||||||
|
currentRows[targetRow] = {
|
||||||
|
firstName: "",
|
||||||
|
lastName: "",
|
||||||
|
street: "",
|
||||||
|
houseNumber: "",
|
||||||
|
zip: "",
|
||||||
|
city: "",
|
||||||
|
country: isDirectShipping ? "Deutschland" : "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
currentRows[targetRow][key] = cellValue;
|
||||||
|
|
||||||
|
// DOM direkt aktualisieren (kein Re-Render nötig)
|
||||||
|
const input = table.querySelector(
|
||||||
|
`input[data-row="${targetRow}"][data-col="${key}"]`,
|
||||||
|
);
|
||||||
|
if (input) input.value = cellValue;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (pastedCells > 0) {
|
// Globalen State DIREKT aktualisieren (OHNE dispatch/render)
|
||||||
hasLocalChanges = false; // Paste übernimmt - keine alten Änderungen mehr relevant
|
globalState.recipientRows = currentRows;
|
||||||
dispatch({ type: "SET_RECIPIENT_ROWS", rows: newData });
|
table._rows = currentRows;
|
||||||
}
|
|
||||||
|
// Für Persistenz markieren
|
||||||
|
markPersistDirty();
|
||||||
});
|
});
|
||||||
|
|
||||||
return table;
|
return table;
|
||||||
@@ -4174,6 +4228,9 @@ function renderRecipientsTable(state, dispatch) {
|
|||||||
{ class: "sk-table sk-table-pasteable", tabindex: "0" },
|
{ class: "sk-table sk-table-pasteable", tabindex: "0" },
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
// Rows-Referenz am Table speichern für Event-Handler
|
||||||
|
table._rows = rows;
|
||||||
|
|
||||||
table.appendChild(
|
table.appendChild(
|
||||||
h("thead", {}, [
|
h("thead", {}, [
|
||||||
h("tr", {}, [
|
h("tr", {}, [
|
||||||
@@ -4200,8 +4257,9 @@ function renderRecipientsTable(state, dispatch) {
|
|||||||
"data-col": key,
|
"data-col": key,
|
||||||
"data-sk-focus": `freeaddr.${key}.${idx}`,
|
"data-sk-focus": `freeaddr.${key}.${idx}`,
|
||||||
oninput: (e) => {
|
oninput: (e) => {
|
||||||
if (!rows[idx]) {
|
const currentRows = table._rows;
|
||||||
rows[idx] = {
|
if (!currentRows[idx]) {
|
||||||
|
currentRows[idx] = {
|
||||||
line1: "",
|
line1: "",
|
||||||
line2: "",
|
line2: "",
|
||||||
line3: "",
|
line3: "",
|
||||||
@@ -4209,7 +4267,13 @@ function renderRecipientsTable(state, dispatch) {
|
|||||||
line5: "",
|
line5: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
rows[idx][key] = e.target.value;
|
currentRows[idx][key] = e.target.value;
|
||||||
|
|
||||||
|
// Auch globalen State aktualisieren für SYNC_GLOBAL_STATE
|
||||||
|
const globalState = window.currentGlobalState;
|
||||||
|
if (globalState) {
|
||||||
|
globalState.freeAddressRows = currentRows;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
@@ -4231,7 +4295,7 @@ function renderRecipientsTable(state, dispatch) {
|
|||||||
let hasLocalChanges = false;
|
let hasLocalChanges = false;
|
||||||
const saveLocalChanges = () => {
|
const saveLocalChanges = () => {
|
||||||
if (hasLocalChanges) {
|
if (hasLocalChanges) {
|
||||||
dispatch({ type: "SET_FREE_ADDRESS_ROWS", rows: [...rows] });
|
dispatch({ type: "SET_FREE_ADDRESS_ROWS", rows: [...table._rows] });
|
||||||
hasLocalChanges = false;
|
hasLocalChanges = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -4253,7 +4317,7 @@ function renderRecipientsTable(state, dispatch) {
|
|||||||
hasLocalChanges = true;
|
hasLocalChanges = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Paste-Handler
|
// Paste-Handler - Daten direkt in globalen State schreiben (OHNE Re-Render)
|
||||||
table.addEventListener("paste", (e) => {
|
table.addEventListener("paste", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const text = e.clipboardData.getData("text/plain");
|
const text = e.clipboardData.getData("text/plain");
|
||||||
@@ -4281,16 +4345,28 @@ function renderRecipientsTable(state, dispatch) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const keys = ["line1", "line2", "line3", "line4", "line5"];
|
const keys = ["line1", "line2", "line3", "line4", "line5"];
|
||||||
const newData = [];
|
|
||||||
for (let i = 0; i < want; i++) {
|
// Aktuellen globalen State holen
|
||||||
newData.push(
|
const globalState = window.currentGlobalState;
|
||||||
rows[i]
|
if (!globalState) return;
|
||||||
? { ...rows[i] }
|
|
||||||
: { line1: "", line2: "", line3: "", line4: "", line5: "" },
|
// Kopie der aktuellen Rows erstellen
|
||||||
);
|
const currentRows = Array.isArray(globalState.freeAddressRows)
|
||||||
|
? globalState.freeAddressRows.map((r) => ({ ...r }))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// Sicherstellen dass genug Zeilen existieren
|
||||||
|
while (currentRows.length < want) {
|
||||||
|
currentRows.push({
|
||||||
|
line1: "",
|
||||||
|
line2: "",
|
||||||
|
line3: "",
|
||||||
|
line4: "",
|
||||||
|
line5: "",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let pastedCells = 0;
|
// Werte in Daten UND direkt ins DOM schreiben
|
||||||
pasteRows.forEach((rowText, rowOffset) => {
|
pasteRows.forEach((rowText, rowOffset) => {
|
||||||
const targetRow = startRow + rowOffset;
|
const targetRow = startRow + rowOffset;
|
||||||
if (targetRow >= want) return;
|
if (targetRow >= want) return;
|
||||||
@@ -4299,15 +4375,34 @@ function renderRecipientsTable(state, dispatch) {
|
|||||||
cells.forEach((cellValue, colOffset) => {
|
cells.forEach((cellValue, colOffset) => {
|
||||||
const targetCol = startCol + colOffset;
|
const targetCol = startCol + colOffset;
|
||||||
if (targetCol >= keys.length) return;
|
if (targetCol >= keys.length) return;
|
||||||
newData[targetRow][keys[targetCol]] = cellValue;
|
const key = keys[targetCol];
|
||||||
pastedCells++;
|
|
||||||
|
// Daten aktualisieren
|
||||||
|
if (!currentRows[targetRow]) {
|
||||||
|
currentRows[targetRow] = {
|
||||||
|
line1: "",
|
||||||
|
line2: "",
|
||||||
|
line3: "",
|
||||||
|
line4: "",
|
||||||
|
line5: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
currentRows[targetRow][key] = cellValue;
|
||||||
|
|
||||||
|
// DOM direkt aktualisieren (kein Re-Render nötig)
|
||||||
|
const input = table.querySelector(
|
||||||
|
`input[data-row="${targetRow}"][data-col="${key}"]`,
|
||||||
|
);
|
||||||
|
if (input) input.value = cellValue;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (pastedCells > 0) {
|
// Globalen State DIREKT aktualisieren (OHNE dispatch/render)
|
||||||
hasLocalChanges = false; // Paste übernimmt - keine alten Änderungen mehr relevant
|
globalState.freeAddressRows = currentRows;
|
||||||
dispatch({ type: "SET_FREE_ADDRESS_ROWS", rows: newData });
|
table._rows = currentRows;
|
||||||
}
|
|
||||||
|
// Für Persistenz markieren
|
||||||
|
markPersistDirty();
|
||||||
});
|
});
|
||||||
|
|
||||||
return table;
|
return table;
|
||||||
@@ -4346,6 +4441,7 @@ function renderRecipientsTable(state, dispatch) {
|
|||||||
setData: (dispatch, data) => {
|
setData: (dispatch, data) => {
|
||||||
dispatch({ type: "SET_FREE_ADDRESS_ROWS", rows: data });
|
dispatch({ type: "SET_FREE_ADDRESS_ROWS", rows: data });
|
||||||
},
|
},
|
||||||
|
stateKey: "freeAddressRows",
|
||||||
rowCount: () => want,
|
rowCount: () => want,
|
||||||
exportFilename: "freie_adressen.csv",
|
exportFilename: "freie_adressen.csv",
|
||||||
helpText: "Geben Sie bis zu 5 Zeilen pro Empfänger ein.",
|
helpText: "Geben Sie bis zu 5 Zeilen pro Empfänger ein.",
|
||||||
@@ -4390,6 +4486,7 @@ function renderRecipientsTable(state, dispatch) {
|
|||||||
setData: (dispatch, data) => {
|
setData: (dispatch, data) => {
|
||||||
dispatch({ type: "SET_RECIPIENT_ROWS", rows: data });
|
dispatch({ type: "SET_RECIPIENT_ROWS", rows: data });
|
||||||
},
|
},
|
||||||
|
stateKey: "recipientRows",
|
||||||
rowCount: () => want,
|
rowCount: () => want,
|
||||||
exportFilename: "empfaenger.csv",
|
exportFilename: "empfaenger.csv",
|
||||||
helpText:
|
helpText:
|
||||||
@@ -4585,6 +4682,22 @@ function renderCombinedPlaceholderTable(
|
|||||||
// Nur lokal speichern - kein Re-Render!
|
// Nur lokal speichern - kein Re-Render!
|
||||||
localValues[name][row] = e.target.value;
|
localValues[name][row] = e.target.value;
|
||||||
hasLocalChanges = true;
|
hasLocalChanges = true;
|
||||||
|
|
||||||
|
// Auch globalen State aktualisieren für SYNC_GLOBAL_STATE
|
||||||
|
const globalState = window.currentGlobalState;
|
||||||
|
if (globalState) {
|
||||||
|
if (!globalState.placeholderValues) {
|
||||||
|
globalState.placeholderValues = {};
|
||||||
|
}
|
||||||
|
if (!globalState.placeholderValues[name]) {
|
||||||
|
globalState.placeholderValues[name] = [];
|
||||||
|
}
|
||||||
|
globalState.placeholderValues[name][row] = e.target.value;
|
||||||
|
// Array auf requiredRowCount-Länge bringen (für validateStep)
|
||||||
|
while (globalState.placeholderValues[name].length < want) {
|
||||||
|
globalState.placeholderValues[name].push("");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -4624,7 +4737,16 @@ function renderCombinedPlaceholderTable(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alle Änderungen in localValues sammeln und Inputs aktualisieren
|
// Aktuellen globalen State holen
|
||||||
|
const globalState = window.currentGlobalState;
|
||||||
|
if (!globalState) return;
|
||||||
|
|
||||||
|
// Kopie der aktuellen Platzhalter-Werte erstellen
|
||||||
|
const currentValues = globalState.placeholderValues
|
||||||
|
? JSON.parse(JSON.stringify(globalState.placeholderValues))
|
||||||
|
: {};
|
||||||
|
|
||||||
|
// Alle Änderungen in currentValues sammeln und Inputs aktualisieren
|
||||||
let pastedCells = 0;
|
let pastedCells = 0;
|
||||||
pasteRows.forEach((rowText, rowOffset) => {
|
pasteRows.forEach((rowText, rowOffset) => {
|
||||||
const targetRow = startRow + rowOffset;
|
const targetRow = startRow + rowOffset;
|
||||||
@@ -4640,7 +4762,11 @@ function renderCombinedPlaceholderTable(
|
|||||||
|
|
||||||
// Nur editierbare Spalten aktualisieren
|
// Nur editierbare Spalten aktualisieren
|
||||||
if (!isReadOnly) {
|
if (!isReadOnly) {
|
||||||
|
// Daten aktualisieren
|
||||||
|
if (!currentValues[name]) currentValues[name] = [];
|
||||||
|
currentValues[name][targetRow] = cellValue;
|
||||||
localValues[name][targetRow] = cellValue;
|
localValues[name][targetRow] = cellValue;
|
||||||
|
|
||||||
// Input-Element direkt aktualisieren
|
// Input-Element direkt aktualisieren
|
||||||
const input = table.querySelector(
|
const input = table.querySelector(
|
||||||
`input[data-row="${targetRow}"][data-col="${name}"]`,
|
`input[data-row="${targetRow}"][data-col="${name}"]`,
|
||||||
@@ -4651,10 +4777,15 @@ function renderCombinedPlaceholderTable(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Nach Paste: Einmal speichern
|
// Globalen State DIREKT aktualisieren (OHNE dispatch/render)
|
||||||
if (pastedCells > 0) {
|
if (pastedCells > 0) {
|
||||||
hasLocalChanges = true;
|
// Arrays auf want-Länge normalisieren (für validateStep)
|
||||||
saveLocalChanges();
|
for (const name of Object.keys(currentValues)) {
|
||||||
|
while (currentValues[name].length < want)
|
||||||
|
currentValues[name].push("");
|
||||||
|
}
|
||||||
|
globalState.placeholderValues = currentValues;
|
||||||
|
markPersistDirty();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -4827,6 +4958,22 @@ function renderPlaceholderTable(
|
|||||||
oninput: (e) => {
|
oninput: (e) => {
|
||||||
// Wert nur lokal speichern (kein Re-Render)
|
// Wert nur lokal speichern (kein Re-Render)
|
||||||
localValues[name][row] = e.target.value;
|
localValues[name][row] = e.target.value;
|
||||||
|
|
||||||
|
// Auch globalen State aktualisieren für SYNC_GLOBAL_STATE
|
||||||
|
const globalState = window.currentGlobalState;
|
||||||
|
if (globalState) {
|
||||||
|
if (!globalState.placeholderValues) {
|
||||||
|
globalState.placeholderValues = {};
|
||||||
|
}
|
||||||
|
if (!globalState.placeholderValues[name]) {
|
||||||
|
globalState.placeholderValues[name] = [];
|
||||||
|
}
|
||||||
|
globalState.placeholderValues[name][row] = e.target.value;
|
||||||
|
// Array auf requiredRowCount-Länge bringen (für validateStep)
|
||||||
|
while (globalState.placeholderValues[name].length < want) {
|
||||||
|
globalState.placeholderValues[name].push("");
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@@ -4890,7 +5037,16 @@ function renderPlaceholderTable(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alle Änderungen in localValues sammeln
|
// Aktuellen globalen State holen
|
||||||
|
const globalState = window.currentGlobalState;
|
||||||
|
if (!globalState) return;
|
||||||
|
|
||||||
|
// Kopie der aktuellen Platzhalter-Werte erstellen
|
||||||
|
const currentValues = globalState.placeholderValues
|
||||||
|
? JSON.parse(JSON.stringify(globalState.placeholderValues))
|
||||||
|
: {};
|
||||||
|
|
||||||
|
// Alle Änderungen sammeln UND direkt ins DOM schreiben
|
||||||
let pastedCells = 0;
|
let pastedCells = 0;
|
||||||
pasteRows.forEach((rowText, rowOffset) => {
|
pasteRows.forEach((rowText, rowOffset) => {
|
||||||
const targetRow = startRow + rowOffset;
|
const targetRow = startRow + rowOffset;
|
||||||
@@ -4902,15 +5058,31 @@ function renderPlaceholderTable(
|
|||||||
if (targetCol >= clean.length) return;
|
if (targetCol >= clean.length) return;
|
||||||
|
|
||||||
const name = clean[targetCol];
|
const name = clean[targetCol];
|
||||||
|
|
||||||
|
// Daten aktualisieren
|
||||||
|
if (!currentValues[name]) currentValues[name] = [];
|
||||||
|
currentValues[name][targetRow] = cellValue;
|
||||||
localValues[name][targetRow] = cellValue;
|
localValues[name][targetRow] = cellValue;
|
||||||
|
|
||||||
|
// DOM direkt aktualisieren (kein Re-Render nötig)
|
||||||
|
const input = table.querySelector(
|
||||||
|
`input[data-row="${targetRow}"][data-col="${name}"]`,
|
||||||
|
);
|
||||||
|
if (input) input.value = cellValue;
|
||||||
|
|
||||||
pastedCells++;
|
pastedCells++;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Einmal dispatch für alle Änderungen
|
// Globalen State DIREKT aktualisieren (OHNE dispatch/render)
|
||||||
if (pastedCells > 0) {
|
if (pastedCells > 0) {
|
||||||
hasLocalChanges = false; // Paste übernimmt - keine alten Änderungen mehr relevant
|
// Arrays auf want-Länge normalisieren (für validateStep)
|
||||||
dispatch({ type: "SET_PLACEHOLDER_VALUES", values: localValues });
|
for (const name of Object.keys(currentValues)) {
|
||||||
|
while (currentValues[name].length < want)
|
||||||
|
currentValues[name].push("");
|
||||||
|
}
|
||||||
|
globalState.placeholderValues = currentValues;
|
||||||
|
markPersistDirty();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user