Abo Einmalprodukte und Bestätigung abschließen

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin 2026-06-05 15:28:08 +00:00
parent 2bdc9ada3c
commit 2269ce031f
57 changed files with 3647 additions and 371 deletions

297
public/js/iq-abo-onetime.js Normal file
View file

@ -0,0 +1,297 @@
var IqAboOneTime = {
card: "#onetime_order_card",
holder: "#insert_show_onetime_order",
abo_holder: "#insert_show_products_order",
summary_card: "#combined_summary_card",
comp_holder: "#holder_html_view_comp_product",
modal: "#modals-load-content",
oTable: null,
url: null,
btn_modal_add: ".add-onetime-product-basket",
btn_add: ".onetime-add-from-basket",
btn_remove: ".onetime-remove-from-basket",
input_change: ".onetime-input-onchange",
remove_item: ".remove_onetime_from_cart",
confirm_changes: ".onetime-confirm-changes",
discard_changes: ".onetime-discard-changes",
summary_col: "#combined_summary_col",
sticky_gap: 16,
_stickyTicking: false,
init: function () {
var _self = this;
_self.url = $(_self.card).data("onetime-route");
_self.reInitCart();
_self.initSticky();
return _self;
},
initSticky: function () {
var _self = this;
var card = document.querySelector(_self.summary_card);
var col = document.querySelector(_self.summary_col);
if (!card || !col) {
return;
}
var onScrollOrResize = function () {
if (_self._stickyTicking) {
return;
}
_self._stickyTicking = true;
window.requestAnimationFrame(function () {
_self.updateSticky(card, col);
_self._stickyTicking = false;
});
};
// Capture-Phase, damit Scroll-Events beliebiger Scroll-Container erfasst werden.
window.addEventListener("scroll", onScrollOrResize, true);
window.addEventListener("resize", onScrollOrResize);
_self.updateSticky(card, col);
},
resetSticky: function (card) {
card.style.position = "";
card.style.top = "";
card.style.left = "";
card.style.width = "";
card.style.zIndex = "";
},
updateSticky: function (card, col) {
var _self = this;
// Nur im zweispaltigen Layout (lg+) kleben.
if (window.innerWidth < 992) {
_self.resetSticky(card);
return;
}
var colStyle = window.getComputedStyle(col);
var padLeft = parseFloat(colStyle.paddingLeft) || 0;
var padRight = parseFloat(colStyle.paddingRight) || 0;
var colRect = col.getBoundingClientRect();
var contentLeft = colRect.left + padLeft;
var contentWidth = col.clientWidth - padLeft - padRight;
var cardHeight = card.offsetHeight;
var gap = _self.sticky_gap;
if (colRect.top >= gap) {
// Noch nicht über den oberen Rand gescrollt -> normaler Fluss.
_self.resetSticky(card);
return;
}
// Oben anheften, aber maximal bis zum Ende der Spalte.
var topPos = gap;
var maxTop = colRect.bottom - cardHeight;
if (maxTop < gap) {
topPos = maxTop;
}
card.style.position = "fixed";
card.style.top = topPos + "px";
card.style.left = contentLeft + "px";
card.style.width = contentWidth + "px";
card.style.zIndex = "1020";
},
setDatabase: function (oTable) {
this.oTable = oTable;
},
reInitModal: function () {
var _self = this;
$(_self.oTable)
.find(_self.btn_modal_add)
.off("click")
.on("click", function () {
_self.addFromModal($(this));
});
},
reInitCart: function () {
var _self = this;
var $holder = $(_self.holder);
$holder
.find(_self.btn_add)
.off("click")
.on("click", function () {
_self.changeQty($(this), 1);
});
$holder
.find(_self.btn_remove)
.off("click")
.on("click", function () {
_self.changeQty($(this), -1);
});
$holder
.find(_self.input_change)
.off("change")
.on("change", function () {
_self.updateInput($(this));
});
$holder
.find(_self.remove_item)
.off("click")
.on("click", function (event) {
event.preventDefault();
_self.removeItem($(this));
});
$holder
.find(_self.confirm_changes)
.off("click")
.on("click", function () {
_self.confirmChanges();
});
$holder
.find(_self.discard_changes)
.off("click")
.on("click", function () {
_self.discardChanges();
});
},
addFromModal: function (_obj) {
var _self = this;
var $modal = $(_self.modal);
$modal.one("hidden.bs.modal", function () {
_self
.performRequest({
action: "add",
product_id: _obj.data("product-id"),
})
.done(_self.refreshView);
});
$modal.modal("hide");
},
changeQty: function (_obj, delta) {
var _self = this;
var itemId = _obj.data("onetime-item-id");
var input = $(_self.holder).find(
'input[name="onetime_qty_' + itemId + '"]'
);
var qty = _self.checkNumber(parseInt(input.val(), 10) + delta);
input.val(qty);
_self
.performRequest({
action: "update",
one_time_item_id: itemId,
qty: qty,
})
.done(_self.refreshView);
},
updateInput: function (_obj) {
var _self = this;
var qty = _self.checkNumber(parseInt(_obj.val(), 10));
_obj.val(qty);
_self
.performRequest({
action: "update",
one_time_item_id: _obj.data("onetime-item-id"),
qty: qty,
})
.done(_self.refreshView);
},
removeItem: function (_obj) {
var _self = this;
_self
.performRequest({
action: "remove",
one_time_item_id: _obj.data("onetime-item-id"),
})
.done(_self.refreshView);
},
confirmChanges: function () {
var _self = this;
_self
.performRequest({
action: "confirm",
})
.done(_self.refreshView);
},
discardChanges: function () {
var _self = this;
_self
.performRequest({
action: "discard",
})
.done(_self.refreshView);
},
refreshView: function (data) {
var _self = IqAboOneTime;
if (data.html_onetime) {
$(_self.holder).html(data.html_onetime);
}
if (data.html_abo) {
$(_self.abo_holder).html(data.html_abo);
}
if (data.html_summary) {
$(_self.summary_card).html(data.html_summary);
}
if (data.html_comp !== undefined) {
$(_self.comp_holder).html(data.html_comp);
}
$(_self.modal).modal("hide");
_self.reInitCart();
if (typeof iqModalCart !== "undefined" && iqModalCart.reInitCart) {
iqModalCart.reInitCart($(_self.abo_holder));
iqModalCart.reInitCart($(_self.comp_holder));
}
var card = document.querySelector(_self.summary_card);
var col = document.querySelector(_self.summary_col);
if (card && col) {
_self.updateSticky(card, col);
}
},
performRequest: function (data) {
var _self = this;
return $.ajax({
url: _self.url,
data: data,
type: "POST",
dataType: "json",
cache: false,
headers: {
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
},
}).fail(function (jqXHR) {
var msg =
jqXHR.responseJSON && jqXHR.responseJSON.message
? jqXHR.responseJSON.message
: "Error";
var errorEl = document.getElementById("insert_onetime_error_message");
if (!errorEl) {
errorEl = document.createElement("div");
errorEl.id = "insert_onetime_error_message";
errorEl.className = "alert alert-danger mt-2";
var holder = document.querySelector("#insert_show_onetime_order");
if (holder) {
holder.insertBefore(errorEl, holder.firstChild);
}
}
errorEl.textContent = msg;
});
},
checkNumber: function (number) {
if (number < 1 || isNaN(number)) {
return 1;
}
if (number > 100) {
return 100;
}
return number;
},
};

View file

@ -45,16 +45,66 @@ var IqModalCart = {
}
});
},
showConfirm: function(productName, productPrice, qtyInfo, callback) {
parseCurrency: function(value) {
if (value === undefined || value === null) {
return null;
}
var normalized = String(value)
.replace(/<[^>]*>/g, "")
.replace(/&euro;/g, "")
.replace(/€/g, "")
.replace(/\s/g, "")
.replace(/\./g, "")
.replace(",", ".");
var amount = parseFloat(normalized);
return isNaN(amount) ? null : amount;
},
formatCurrency: function(value) {
if (value === undefined || value === null || isNaN(value)) {
return "-";
}
return (
value.toLocaleString("de-DE", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}) + " €"
);
},
showConfirm: function(productName, productPrice, qtyInfo, callback, options) {
var _self = this;
var $confirmModal = $(_self.confirmModal);
var titleKey = _self.addOnlyMode ? "title-add-only" : "title-normal";
var warningKey = _self.addOnlyMode ? "warning-add-only" : "warning-normal";
$("#modal-confirm-add-label").text($confirmModal.data(titleKey));
$("#confirm-add-warning-text").text($confirmModal.data(warningKey));
var confirmOptions = options || {};
var confirmType = confirmOptions.type || "add";
var additionalCost = _self.parseCurrency(productPrice);
var currentTotal = _self.parseCurrency(
$("#value-amount").text() || $confirmModal.data("current-total"),
);
var newTotal =
currentTotal !== null && additionalCost !== null
? currentTotal + additionalCost
: null;
var title = $confirmModal.data("title-" + confirmType);
var info = $confirmModal.data("info-" + confirmType);
$("#modal-confirm-add-label").text(title || $confirmModal.data("title-add"));
$("#confirm-add-info-text").text(info || $confirmModal.data("info-add"));
$("#confirm-add-product-name").text(productName || "-");
$("#confirm-add-product-price").html(productPrice || "-");
$("#confirm-add-product-price").html(
additionalCost !== null
? "+ " +
_self.formatCurrency(additionalCost) +
" " +
($confirmModal.data("per-delivery") || "")
: productPrice || "-",
);
$("#confirm-add-qty-info").text(qtyInfo || "");
$("#confirm-add-new-total").text(_self.formatCurrency(newTotal));
$("#confirm-add-next-billing-date").text(
$confirmModal.data("next-billing-date") || "-",
);
_self.pendingAction = callback;
$confirmModal.modal("show");
},
@ -76,15 +126,21 @@ var IqModalCart = {
var productPrice = _obj.data("product-price") || "";
var $modal = $(_self.modal);
$modal.one("hidden.bs.modal", function() {
_self.showConfirm(productName, productPrice, "1x", function() {
_self
.performRequest({
product_id: _obj.data("product-id"),
qty: 1,
action: "addProduct",
})
.done(_self.refreshView);
});
_self.showConfirm(
productName,
productPrice,
"1x",
function() {
_self
.performRequest({
product_id: _obj.data("product-id"),
qty: 1,
action: "addProduct",
})
.done(_self.refreshView);
},
{ type: "add" },
);
});
$modal.modal("hide");
},
@ -117,9 +173,11 @@ var IqModalCart = {
_self.remove_from_cart($(this), _obj);
});
if (_self.is_for === "me" || _self.is_for === "abo-me") {
$('input[name^="' + _self.comp_products + '"]').on("change", function() {
_self.update_comp_product($(this));
});
$('input[name^="' + _self.comp_products + '"]')
.off("change")
.on("change", function() {
_self.update_comp_product($(this));
});
}
},
update_comp_product: function(_obj) {
@ -149,10 +207,16 @@ var IqModalCart = {
var productPrice = _obj.data("product-price") || "";
var qtyInfo = currentQty + " \u2192 " + qty;
console.log(qtyInfo);
_self.showConfirm(productName, productPrice, qtyInfo, function() {
input.val(qty);
_self.update_cart(_holder, _obj, qty);
});
_self.showConfirm(
productName,
productPrice,
qtyInfo,
function() {
input.val(qty);
_self.update_cart(_holder, _obj, qty);
},
{ type: "increase" },
);
},
remove_product: function(_obj, _holder) {
var _self = this;
@ -200,12 +264,16 @@ var IqModalCart = {
var obj = $(_self.cart_holder);
obj.html(data.html_cart);
$(_self.insert_show_total_order).html(data.html_total);
if (data.html_summary) {
$("#combined_summary_card").html(data.html_summary);
}
$(_self.modal).modal("hide");
if ($(_self.comp_holder)) {
if (data.html_comp !== undefined) {
$(_self.comp_holder).html(data.html_comp);
}
if ($("#value-amount")) {
$("#value-amount").html(data.amount);
$(_self.confirmModal).data("current-total", data.amount);
}
_self.reInitCart(obj);
},

View file

@ -12,6 +12,8 @@ var IqShoppingCart = {
oTable: null,
table_input: '.table-input-event-onchange',
cart_input: '.cart-input-event-onchange',
cart_add: '.cart-add-event',
cart_remove: '.cart-remove-event',
remove_item: '.remove_item_form_cart',
shipping_state: '#change_shipping_state',
comp_products: 'switchers-comp-product',
@ -57,6 +59,12 @@ var IqShoppingCart = {
$(_self.cart_input).on('change', function(){
_self.update_input_cart($(this));
});
$(_self.cart_add).off('click').on('click', function(){
_self.change_cart_qty($(this), 1);
});
$(_self.cart_remove).off('click').on('click', function(){
_self.change_cart_qty($(this), -1);
});
$(_self.remove_item).on('click', function(event){
event.preventDefault();
_self.update_cart_database($(this).data('product-id'), 0);
@ -88,6 +96,18 @@ var IqShoppingCart = {
_obj.val(qty);
_self.update_cart_database(_obj.data('product-id'), qty);
},
/**
* Plus-/Minus-Buttons im Warenkorb: Menge nicht optimistisch erhöhen, sondern
* erst nach Server-Antwort (Re-Render) anzeigen. So bleibt bei einem Stop
* (z. B. Maximalgewicht) der Zähler stehen und es erscheint ein Inline-Hinweis.
*/
change_cart_qty: function (_obj, delta){
var _self = this;
var productId = _obj.data('product-id');
var input = $(_self.card_holder).find('.cart-input-event-onchange[data-product-id="'+productId+'"]');
var qty = _self.checkNumber(parseInt(input.val(), 10) + delta);
_self.update_cart_database(productId, qty);
},
update_comp_product: function (_obj){
var _self = this;
_self.performRequest({comp_product_id: _obj.val(), comp_num: _obj.data('comp_num'), count_comp_products: $('input[name="'+_self.count_comp_products+'"]').val(), action: 'updateCompProduct'})
@ -170,10 +190,24 @@ var IqShoppingCart = {
}
},
showCartError: function (message){
var _self = this;
var errorEl = document.getElementById('insert_show_error_message');
if (!errorEl) {
errorEl = document.createElement('div');
errorEl.id = 'insert_show_error_message';
errorEl.className = 'alert alert-danger mt-2';
var cartContent = document.getElementById('cartContent');
if (cartContent) {
cartContent.insertBefore(errorEl, cartContent.firstChild);
}
}
errorEl.textContent = message;
},
refreshItemsAndView: function (data){
var _self = IqShoppingCart;
if (data && data.response === false && data.message) {
alert(data.message);
_self.showCartError(data.message);
return;
}
$(_self.card_holder).html(data.html_card);
@ -185,7 +219,7 @@ var IqShoppingCart = {
refreshDatabaseAndView: function (data) {
var _self = IqShoppingCart;
if (data && data.response === false && data.message) {
alert(data.message);
_self.showCartError(data.message);
return;
}
$(_self.card_holder).html(data.html_card);