Files
2026-05-04 09:40:27 +02:00

221 lines
7.2 KiB
JavaScript

/* global SkriftConfigurator */
import {
createInitialState,
deriveContextFromUrl,
reducer,
STEPS,
} from "./configurator-state.js?ver=0.3.1";
import {
render,
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() {
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 = [];
const cleanup = () => {
flushAllTables(); // Tabellen-Daten speichern vor Cleanup/Seiten-Unload
cleanupHandlers.forEach((handler) => handler());
cleanupHandlers.length = 0;
if (window.envelopePreviewManager) {
window.envelopePreviewManager.destroy();
}
if (window.contentPreviewManager) {
window.contentPreviewManager.destroy();
}
};
// Cleanup bei Seiten-Unload
window.addEventListener("beforeunload", cleanup);
cleanupHandlers.push(() =>
window.removeEventListener("beforeunload", cleanup),
);
const ctx = deriveContextFromUrl(window.location.search);
let state = createInitialState(ctx);
const dom = {
topbar: document.getElementById("sk-topbar"),
stepper: document.getElementById("sk-stepper"),
form: document.getElementById("sk-form"),
prev: document.getElementById("sk-prev"),
next: document.getElementById("sk-next"),
preview: document.getElementById("sk-preview"),
previewMobile: document.getElementById("sk-preview-mobile"),
contactMobile: document.getElementById("sk-contact-mobile"),
};
// Preview Manager initialisieren
const api = window.SkriftBackendAPI;
if (api) {
window.envelopePreviewManager = new PreviewManager(api);
window.contentPreviewManager = new PreviewManager(api);
}
const dispatch = (action) => {
// Scroll-Position VOR dem State-Update speichern
const scrollY = window.scrollY;
const scrollX = window.scrollX;
state = reducer(state, action);
render({ state, dom, dispatch });
// Scroll-Position NACH dem Render wiederherstellen
// Nur bei Actions die NICHT den Step wechseln (Navigation)
const navigationActions = ["NAV_NEXT", "NAV_PREV", "SET_STEP"];
if (!navigationActions.includes(action.type)) {
// requestAnimationFrame stellt sicher, dass das DOM komplett gerendert ist
requestAnimationFrame(() => {
window.scrollTo(scrollX, scrollY);
});
}
};
// Event-Handler mit Cleanup-Tracking
const prevClickHandler = () => {
flushAllTables(); // Tabellen-Daten speichern vor Navigation
dispatch({ type: "NAV_PREV" });
};
// Next-Handler mit Validierung bei Content-Step
const nextClickHandler = async () => {
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
const currentState = window.currentGlobalState || state;
// Bei Content-Step: Textlänge validieren und alle Previews generieren
if (currentState.step === STEPS.CONTENT) {
const previewManager = window.contentPreviewManager;
if (previewManager) {
showValidationOverlay();
try {
const validation =
await previewManager.validateTextLength(currentState);
if (!validation.valid) {
hideValidationOverlay();
// Bei Overflow: Warnung anzeigen
if (
validation.overflowFiles &&
validation.overflowFiles.length > 0
) {
showOverflowWarning(validation.overflowFiles, dom.form);
} else if (validation.error) {
// Bei Fehler (z.B. keine Anfragen mehr): Fehlermeldung anzeigen
showValidationError(validation.error, dom.form);
}
return; // Nicht weiter navigieren
}
// Nach erfolgreicher Validierung: Umschlag-Previews generieren (gleiche Session)
// So sind alle Dokumente im Cache wenn die Bestellung finalisiert wird
const envelopeManager = window.envelopePreviewManager;
if (envelopeManager && currentState.answers?.envelope === true) {
try {
envelopeManager.previewCount =
parseInt(currentState.answers?.quantity) || 1;
await envelopeManager.loadAllPreviews(currentState, true, true);
console.log("[App] Envelope previews generated for cache");
} catch (envError) {
console.error(
"[App] Envelope preview generation failed:",
envError,
);
// Nicht blockieren - Umschläge sind nicht kritisch für Navigation
}
}
hideValidationOverlay();
} catch (error) {
hideValidationOverlay();
console.error("[App] Validation error:", error);
showValidationError(
error.message || "Validierung fehlgeschlagen",
dom.form,
);
return; // Nicht weiter navigieren
}
}
}
dispatch({ type: "NAV_NEXT" });
};
dom.prev.addEventListener("click", prevClickHandler);
dom.next.addEventListener("click", nextClickHandler);
cleanupHandlers.push(() => {
dom.prev.removeEventListener("click", prevClickHandler);
dom.next.removeEventListener("click", nextClickHandler);
});
// Keyboard Navigation für Previews (nur innerhalb des aktuellen Batches)
const keydownHandler = (e) => {
if (
window.envelopePreviewManager &&
window.envelopePreviewManager.currentBatchPreviews.length > 0
) {
window.envelopePreviewManager.handleKeyboardNavigation(
e,
state,
dom.preview,
true,
);
}
if (
window.contentPreviewManager &&
window.contentPreviewManager.currentBatchPreviews.length > 0
) {
window.contentPreviewManager.handleKeyboardNavigation(
e,
state,
dom.preview,
false,
);
}
};
document.addEventListener("keydown", keydownHandler);
cleanupHandlers.push(() =>
document.removeEventListener("keydown", keydownHandler),
);
render({ state, dom, dispatch });
// Konfigurator sichtbar machen nachdem erster Render abgeschlossen ist
requestAnimationFrame(() => {
root.classList.add("sk-ready");
});
})();