@bigz-app/booking-widget 0.3.5 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6946,7 +6946,7 @@
6946
6946
  // Convert double underscores to HTML underline tags for React Markdown
6947
6947
  return markdown.replace(/__([^_]+)__/g, "<u>$1</u>");
6948
6948
  };
6949
- const IconCheck$1 = ({ size = 16, color = "#10b981" }) => (u$2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: u$2("polyline", { points: "20 6 9 17 4 12" }) }));
6949
+ const IconCheck$2 = ({ size = 16, color = "#10b981" }) => (u$2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: u$2("polyline", { points: "20 6 9 17 4 12" }) }));
6950
6950
  const IconWave$1 = ({ size = 20, color = "#0ea5e9" }) => (u$2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [u$2("path", { d: "M2 18c2-2 6-2 8 0s6 2 8 0 6-2 8 0" }), u$2("path", { d: "M2 12c2-2 6-2 8 0s6 2 8 0 6-2 8 0" }), u$2("path", { d: "M2 6c2-2 6-2 8 0s6 2 8 0 6-2 8 0" })] }));
6951
6951
  function EventTypeDetailsDialog({ isOpen, onClose, eventType, onEventTypeSelect, }) {
6952
6952
  if (!isOpen || !eventType)
@@ -6993,7 +6993,7 @@
6993
6993
  fontSize: "16px",
6994
6994
  lineHeight: "1.6",
6995
6995
  color: "var(--bw-text-color)",
6996
- }, children: [u$2("div", { style: { marginTop: "4px", flexShrink: 0 }, children: u$2(IconCheck$1, { size: 16, color: "var(--bw-success-color)" }) }), u$2("span", { children: highlight.trim() })] }, index))) }) }) }))] }), eventType.description && (u$2("div", { style: {
6996
+ }, children: [u$2("div", { style: { marginTop: "4px", flexShrink: 0 }, children: u$2(IconCheck$2, { size: 16, color: "var(--bw-success-color)" }) }), u$2("span", { children: highlight.trim() })] }, index))) }) }) }))] }), eventType.description && (u$2("div", { style: {
6997
6997
  marginBottom: "24px",
6998
6998
  color: "var(--bw-text-muted)",
6999
6999
  fontSize: "16px",
@@ -7302,7 +7302,7 @@
7302
7302
  // Custom minimal SVG icons (Lucide-style)
7303
7303
  const IconClock = ({ size = 16, color = "#10b981" }) => (u$2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [u$2("circle", { cx: "12", cy: "12", r: "10" }), u$2("polyline", { points: "12 6 12 12 16 14" })] }));
7304
7304
  const IconCalendar = ({ size = 16, color = "#3b82f6" }) => (u$2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [u$2("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }), u$2("line", { x1: "16", y1: "2", x2: "16", y2: "6" }), u$2("line", { x1: "8", y1: "2", x2: "8", y2: "6" }), u$2("line", { x1: "3", y1: "10", x2: "21", y2: "10" })] }));
7305
- const IconCheck = ({ size = 16, color = "#10b981" }) => (u$2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: u$2("polyline", { points: "20 6 9 17 4 12" }) }));
7305
+ const IconCheck$1 = ({ size = 16, color = "#10b981" }) => (u$2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: u$2("polyline", { points: "20 6 9 17 4 12" }) }));
7306
7306
  // Wave icon for booking action
7307
7307
  const IconWave = ({ size = 20, color = "#0ea5e9" }) => (u$2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [u$2("path", { d: "M2 18c2-2 6-2 8 0s6 2 8 0 6-2 8 0" }), u$2("path", { d: "M2 12c2-2 6-2 8 0s6 2 8 0 6-2 8 0" }), u$2("path", { d: "M2 6c2-2 6-2 8 0s6 2 8 0 6-2 8 0" })] }));
7308
7308
  // Loading skeleton component that matches the actual design
@@ -7766,7 +7766,7 @@
7766
7766
  color: "var(--bw-text-muted)",
7767
7767
  position: "relative",
7768
7768
  maxWidth: "100%",
7769
- }, children: [u$2("div", { style: { marginTop: "4px", flexShrink: 0 }, children: u$2(IconCheck, { size: 16, color: "var(--bw-success-color)" }) }), u$2("span", { style: {
7769
+ }, children: [u$2("div", { style: { marginTop: "4px", flexShrink: 0 }, children: u$2(IconCheck$1, { size: 16, color: "var(--bw-success-color)" }) }), u$2("span", { style: {
7770
7770
  textOverflow: "ellipsis",
7771
7771
  overflow: "hidden",
7772
7772
  whiteSpace: "nowrap",
@@ -9196,6 +9196,110 @@
9196
9196
 
9197
9197
  var reactStripe_umdExports = reactStripe_umd.exports;
9198
9198
 
9199
+ // Component for bookings fully covered by gift cards (no Stripe payment needed)
9200
+ function GiftCardOnlyBooking({ config, eventDetails, formData, discountCode, giftCards, onSuccess, onError, }) {
9201
+ const [isLoading, setIsLoading] = d$1(false);
9202
+ const [error, setError] = d$1(null);
9203
+ const handleBooking = async () => {
9204
+ setIsLoading(true);
9205
+ setError(null);
9206
+ try {
9207
+ // Create booking directly without Stripe payment
9208
+ const requestData = {
9209
+ eventInstanceId: config.eventInstanceId || eventDetails.id,
9210
+ organizationId: config.organizationId,
9211
+ participants: formData.participants.filter((p) => p.name?.trim()),
9212
+ discountCode: discountCode?.code,
9213
+ giftCardCodes: giftCards.map((gc) => gc.code),
9214
+ customerName: formData.customerName?.trim(),
9215
+ customerEmail: formData.customerEmail?.trim(),
9216
+ customerPhone: formData.customerPhone?.trim(),
9217
+ comment: formData.comment?.trim(),
9218
+ paymentMethod: "gift_card",
9219
+ };
9220
+ const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-gift-card-booking"), {
9221
+ method: "POST",
9222
+ headers: createApiHeaders(config),
9223
+ body: JSON.stringify(createRequestBody(config, requestData)),
9224
+ });
9225
+ const data = await response.json();
9226
+ if (response.ok) {
9227
+ onSuccess({
9228
+ booking: data.booking,
9229
+ order: data.order,
9230
+ giftCardRedemptions: data.giftCardRedemptions,
9231
+ });
9232
+ }
9233
+ else {
9234
+ setError(data.error || "Fehler beim Erstellen der Buchung");
9235
+ onError(data.error || "Fehler beim Erstellen der Buchung");
9236
+ }
9237
+ }
9238
+ catch (err) {
9239
+ setError(err.message || "Fehler beim Erstellen der Buchung");
9240
+ onError(err.message || "Fehler beim Erstellen der Buchung");
9241
+ }
9242
+ finally {
9243
+ setIsLoading(false);
9244
+ }
9245
+ };
9246
+ const totalGiftCardAmount = giftCards.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0);
9247
+ return (u$2("div", { style: { display: "flex", flexDirection: "column", gap: "var(--bw-spacing)" }, children: [u$2("div", { style: {
9248
+ backgroundColor: "var(--bw-success-color)15",
9249
+ border: "1px solid var(--bw-success-color)40",
9250
+ borderRadius: "var(--bw-border-radius)",
9251
+ padding: "var(--bw-spacing)",
9252
+ }, children: [u$2("div", { style: {
9253
+ display: "flex",
9254
+ alignItems: "center",
9255
+ gap: "8px",
9256
+ marginBottom: "8px",
9257
+ color: "var(--bw-success-color)",
9258
+ fontFamily: "var(--bw-font-family)",
9259
+ fontWeight: "600",
9260
+ }, children: "\uD83C\uDF81 Vollst\u00E4ndig durch Gutschein(e) gedeckt" }), u$2("div", { style: {
9261
+ fontSize: "var(--bw-font-size)",
9262
+ color: "var(--bw-text-muted)",
9263
+ fontFamily: "var(--bw-font-family)",
9264
+ }, children: ["Gutschein-Guthaben: ", formatCurrency(totalGiftCardAmount)] })] }), error && (u$2("div", { style: {
9265
+ backgroundColor: "var(--bw-error-color)15",
9266
+ border: "1px solid var(--bw-error-color)40",
9267
+ borderRadius: "var(--bw-border-radius)",
9268
+ padding: "var(--bw-spacing)",
9269
+ color: "var(--bw-error-color)",
9270
+ fontSize: "var(--bw-font-size)",
9271
+ fontFamily: "var(--bw-font-family)",
9272
+ }, children: ["\u26A0\uFE0F ", error] })), u$2("button", { type: "button", onClick: handleBooking, disabled: isLoading, style: {
9273
+ width: "100%",
9274
+ padding: "12px 24px",
9275
+ backgroundColor: "var(--bw-highlight-color)",
9276
+ color: "#fff",
9277
+ border: "none",
9278
+ borderRadius: "var(--bw-border-radius)",
9279
+ fontSize: "var(--bw-font-size)",
9280
+ fontWeight: "600",
9281
+ cursor: isLoading ? "not-allowed" : "pointer",
9282
+ opacity: isLoading ? 0.6 : 1,
9283
+ transition: "all 0.2s ease",
9284
+ fontFamily: "var(--bw-font-family)",
9285
+ display: "flex",
9286
+ alignItems: "center",
9287
+ justifyContent: "center",
9288
+ gap: "8px",
9289
+ }, children: isLoading ? (u$2(k$3, { children: [u$2("div", { style: {
9290
+ width: "16px",
9291
+ height: "16px",
9292
+ border: "2px solid #fff",
9293
+ borderTopColor: "transparent",
9294
+ borderRadius: "50%",
9295
+ animation: "spin 1s linear infinite",
9296
+ } }), "Buchung wird erstellt..."] })) : ("Mit Gutschein buchen") }), u$2("style", { children: `
9297
+ @keyframes spin {
9298
+ from { transform: rotate(0deg); }
9299
+ to { transform: rotate(360deg); }
9300
+ }
9301
+ ` })] }));
9302
+ }
9199
9303
  const spinner$1 = (borderColor) => (u$2("div", { style: {
9200
9304
  width: "auto",
9201
9305
  height: "auto",
@@ -9212,7 +9316,7 @@
9212
9316
  borderRadius: "50%",
9213
9317
  } }) }));
9214
9318
  // Inner component that uses the Stripe hooks
9215
- function PaymentFormInner({ config, eventDetails, formData, totalAmount, discountCode, onSuccess, onError, }) {
9319
+ function PaymentFormInner({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, }) {
9216
9320
  const stripe = reactStripe_umdExports.useStripe();
9217
9321
  const elements = reactStripe_umdExports.useElements();
9218
9322
  const [isLoading, setIsLoading] = d$1(false);
@@ -9343,7 +9447,7 @@
9343
9447
  ` })] }));
9344
9448
  }
9345
9449
  // Main PaymentForm component that handles payment intent creation and Elements wrapper
9346
- function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode, onSuccess, onError, systemConfig, stripePromise, stripeAppearance, }) {
9450
+ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripePromise, stripeAppearance, }) {
9347
9451
  const [clientSecret, setClientSecret] = d$1(null);
9348
9452
  const [paymentIntentId, setPaymentIntentId] = d$1(null);
9349
9453
  const [isCreatingPaymentIntent, setIsCreatingPaymentIntent] = d$1(false);
@@ -9439,6 +9543,7 @@
9439
9543
  currency: "eur",
9440
9544
  participants: formData.participants.filter((p) => p.name?.trim()),
9441
9545
  discountCode: discountCode?.code,
9546
+ giftCardCodes: giftCards?.map((gc) => gc.code) || [],
9442
9547
  customerName: formData.customerName?.trim(),
9443
9548
  customerEmail: formData.customerEmail?.trim(),
9444
9549
  customerPhone: formData.customerPhone?.trim(),
@@ -9500,8 +9605,21 @@
9500
9605
  formData.customerName,
9501
9606
  totalAmount,
9502
9607
  discountCode,
9608
+ giftCards,
9503
9609
  config,
9504
9610
  ]);
9611
+ // Calculate total gift card coverage
9612
+ const totalGiftCardAmount = giftCards?.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0) || 0;
9613
+ const baseTotal = eventDetails?.price
9614
+ ? eventDetails.price * (formData.participants?.filter((p) => p.name?.trim()).length || 0)
9615
+ : 0;
9616
+ const discountAmount = discountCode?.discountAmount || 0;
9617
+ const amountAfterDiscount = Math.max(0, baseTotal - discountAmount);
9618
+ const isFullyCoveredByGiftCards = totalGiftCardAmount >= amountAfterDiscount && amountAfterDiscount > 0;
9619
+ // If gift cards fully cover the payment, show a simplified booking button
9620
+ if (isFullyCoveredByGiftCards && totalAmount <= 0) {
9621
+ return (u$2(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards || [], onSuccess: onSuccess, onError: onError }));
9622
+ }
9505
9623
  // Show loading state while creating payment intent
9506
9624
  if (isCreatingPaymentIntent || !clientSecret) {
9507
9625
  return (u$2("div", { style: {
@@ -9535,7 +9653,7 @@
9535
9653
  clientSecret,
9536
9654
  appearance: stripeAppearance || { theme: "stripe" },
9537
9655
  locale: config.locale || "de",
9538
- }, children: u$2(PaymentFormInner, { config: config, eventDetails: eventDetails, formData: formData, totalAmount: totalAmount, discountCode: discountCode, onSuccess: (result) => {
9656
+ }, children: u$2(PaymentFormInner, { config: config, eventDetails: eventDetails, formData: formData, totalAmount: totalAmount, discountCode: discountCode, giftCards: giftCards, onSuccess: (result) => {
9539
9657
  // Clear persisted PI data on successful payment
9540
9658
  clearPersistedPaymentIntent();
9541
9659
  setPaymentIntentId(null);
@@ -9740,6 +9858,222 @@
9740
9858
  }, children: children }))] }));
9741
9859
  }
9742
9860
 
9861
+ // Icons
9862
+ const IconTicket = ({ size = 20, color = "currentColor" }) => (u$2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [u$2("path", { d: "M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z" }), u$2("path", { d: "M13 5v2" }), u$2("path", { d: "M13 17v2" }), u$2("path", { d: "M13 11v2" })] }));
9863
+ const IconGift = ({ size = 20, color = "currentColor" }) => (u$2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [u$2("rect", { x: "3", y: "8", width: "18", height: "4", rx: "1" }), u$2("path", { d: "M12 8v13" }), u$2("path", { d: "M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7" }), u$2("path", { d: "M7.5 8a2.5 2.5 0 0 1 0-5A4.8 8 0 0 1 12 8a4.8 8 0 0 1 4.5-5 2.5 2.5 0 0 1 0 5" })] }));
9864
+ const IconCheck = ({ size = 16, color = "currentColor" }) => (u$2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: u$2("polyline", { points: "20 6 9 17 4 12" }) }));
9865
+ const IconX = ({ size = 16, color = "currentColor" }) => (u$2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [u$2("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), u$2("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }));
9866
+ const IconSpinner = ({ size = 16, color = "currentColor" }) => (u$2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { animation: "spin 1s linear infinite" }, children: u$2("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }));
9867
+ function VoucherInput({ config, orderValue, eventInstanceId, customerEmail, onVoucherValidated, appliedVouchers, onRemoveVoucher, disabled = false, }) {
9868
+ const [inputValue, setInputValue] = d$1("");
9869
+ const [isLoading, setIsLoading] = d$1(false);
9870
+ const [error, setError] = d$1(null);
9871
+ const [isExpanded, setIsExpanded] = d$1(false);
9872
+ // Check if a discount code is already applied (only one allowed)
9873
+ const hasDiscountCode = appliedVouchers.some((v) => v.type === "discount");
9874
+ const validateVoucher = q$2(async (code) => {
9875
+ if (!code.trim()) {
9876
+ setError(null);
9877
+ return;
9878
+ }
9879
+ // Check if code is already applied
9880
+ if (appliedVouchers.some((v) => v.code.toUpperCase() === code.toUpperCase())) {
9881
+ setError("Dieser Code wurde bereits angewendet");
9882
+ return;
9883
+ }
9884
+ setIsLoading(true);
9885
+ setError(null);
9886
+ try {
9887
+ const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/validate-voucher"), {
9888
+ method: "POST",
9889
+ headers: createApiHeaders(config),
9890
+ body: JSON.stringify(createRequestBody(config, {
9891
+ code: code.trim(),
9892
+ orderValue: orderValue,
9893
+ eventInstanceId: eventInstanceId,
9894
+ customerEmail: customerEmail,
9895
+ })),
9896
+ });
9897
+ const data = await response.json();
9898
+ if (data.valid && data.voucher) {
9899
+ // Check if trying to add a second discount code
9900
+ if (data.voucher.type === "discount" && hasDiscountCode) {
9901
+ setError("Es kann nur ein Rabattcode verwendet werden");
9902
+ onVoucherValidated(null, "Es kann nur ein Rabattcode verwendet werden");
9903
+ return;
9904
+ }
9905
+ onVoucherValidated(data.voucher);
9906
+ setInputValue("");
9907
+ setError(null);
9908
+ }
9909
+ else {
9910
+ setError(data.error || "Code nicht gefunden oder ungültig");
9911
+ onVoucherValidated(null, data.error);
9912
+ }
9913
+ }
9914
+ catch (err) {
9915
+ const errorMsg = "Fehler beim Validieren des Codes";
9916
+ setError(errorMsg);
9917
+ onVoucherValidated(null, errorMsg);
9918
+ }
9919
+ finally {
9920
+ setIsLoading(false);
9921
+ }
9922
+ }, [
9923
+ config,
9924
+ orderValue,
9925
+ eventInstanceId,
9926
+ customerEmail,
9927
+ appliedVouchers,
9928
+ hasDiscountCode,
9929
+ onVoucherValidated,
9930
+ ]);
9931
+ const handleSubmit = () => {
9932
+ if (inputValue.trim() && !isLoading && !disabled) {
9933
+ validateVoucher(inputValue);
9934
+ }
9935
+ };
9936
+ const handleKeyDown = (e) => {
9937
+ if (e.key === "Enter") {
9938
+ e.preventDefault();
9939
+ if (inputValue.trim() && !isLoading && !disabled) {
9940
+ validateVoucher(inputValue);
9941
+ }
9942
+ }
9943
+ };
9944
+ const inputStyle = {
9945
+ flex: 1,
9946
+ padding: "10px 12px",
9947
+ backgroundColor: "var(--bw-background-color)",
9948
+ border: "1px solid var(--bw-border-color)",
9949
+ borderRadius: "var(--bw-border-radius)",
9950
+ color: "var(--bw-text-color)",
9951
+ fontSize: "var(--bw-font-size)",
9952
+ fontFamily: "var(--bw-font-family)",
9953
+ outline: "none",
9954
+ transition: "all 0.2s ease",
9955
+ textTransform: "uppercase",
9956
+ };
9957
+ const buttonStyle = {
9958
+ padding: "10px 16px",
9959
+ backgroundColor: "var(--bw-highlight-color)",
9960
+ border: "none",
9961
+ borderRadius: "var(--bw-border-radius)",
9962
+ color: "#fff",
9963
+ fontSize: "var(--bw-font-size)",
9964
+ fontFamily: "var(--bw-font-family)",
9965
+ fontWeight: "600",
9966
+ cursor: disabled || isLoading ? "not-allowed" : "pointer",
9967
+ opacity: disabled || isLoading ? 0.6 : 1,
9968
+ transition: "all 0.2s ease",
9969
+ display: "flex",
9970
+ alignItems: "center",
9971
+ justifyContent: "center",
9972
+ gap: "6px",
9973
+ minWidth: "100px",
9974
+ };
9975
+ const appliedVoucherStyle = {
9976
+ display: "flex",
9977
+ alignItems: "center",
9978
+ justifyContent: "space-between",
9979
+ padding: "10px 12px",
9980
+ backgroundColor: "var(--bw-surface-color)",
9981
+ border: "1px solid var(--bw-border-color)",
9982
+ borderRadius: "var(--bw-border-radius)",
9983
+ marginBottom: "8px",
9984
+ };
9985
+ const removeButtonStyle = {
9986
+ background: "none",
9987
+ border: "none",
9988
+ padding: "4px",
9989
+ cursor: "pointer",
9990
+ color: "var(--bw-error-color)",
9991
+ display: "flex",
9992
+ alignItems: "center",
9993
+ justifyContent: "center",
9994
+ borderRadius: "50%",
9995
+ transition: "background-color 0.2s ease",
9996
+ };
9997
+ return (u$2("div", { style: {
9998
+ backgroundColor: "var(--bw-surface-color)",
9999
+ border: "1px solid var(--bw-border-color)",
10000
+ borderRadius: "var(--bw-border-radius)",
10001
+ overflow: "hidden",
10002
+ }, children: [u$2("button", { type: "button", onClick: () => setIsExpanded(!isExpanded), style: {
10003
+ width: "100%",
10004
+ padding: "var(--bw-spacing)",
10005
+ backgroundColor: "transparent",
10006
+ border: "none",
10007
+ cursor: "pointer",
10008
+ display: "flex",
10009
+ alignItems: "center",
10010
+ justifyContent: "space-between",
10011
+ color: "var(--bw-text-color)",
10012
+ fontFamily: "var(--bw-font-family)",
10013
+ fontSize: "var(--bw-font-size)",
10014
+ fontWeight: "500",
10015
+ }, children: [u$2("span", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [u$2(IconTicket, { size: 18, color: "var(--bw-highlight-color)" }), "Rabattcode oder Gutschein", appliedVouchers.length > 0 && (u$2("span", { style: {
10016
+ backgroundColor: "var(--bw-highlight-color)",
10017
+ color: "#fff",
10018
+ padding: "2px 8px",
10019
+ borderRadius: "12px",
10020
+ fontSize: "12px",
10021
+ fontWeight: "600",
10022
+ }, children: appliedVouchers.length }))] }), u$2("span", { style: {
10023
+ transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)",
10024
+ transition: "transform 0.2s ease",
10025
+ }, children: "\u25BC" })] }), isExpanded && (u$2("div", { style: { padding: "0 var(--bw-spacing) var(--bw-spacing)" }, children: [appliedVouchers.length > 0 && (u$2("div", { style: { marginBottom: "12px" }, children: appliedVouchers.map((voucher) => (u$2("div", { style: appliedVoucherStyle, children: [u$2("div", { style: { display: "flex", alignItems: "center", gap: "10px" }, children: [voucher.type === "discount" ? (u$2(IconTicket, { size: 18, color: "var(--bw-success-color)" })) : (u$2(IconGift, { size: 18, color: "var(--bw-success-color)" })), u$2("div", { children: [u$2("div", { style: {
10026
+ fontFamily: "var(--bw-font-family)",
10027
+ fontSize: "var(--bw-font-size)",
10028
+ fontWeight: "600",
10029
+ color: "var(--bw-text-color)",
10030
+ display: "flex",
10031
+ alignItems: "center",
10032
+ gap: "6px",
10033
+ }, children: [u$2("span", { style: { fontFamily: "monospace" }, children: voucher.code }), u$2(IconCheck, { size: 14, color: "var(--bw-success-color)" })] }), u$2("div", { style: {
10034
+ fontFamily: "var(--bw-font-family)",
10035
+ fontSize: "12px",
10036
+ color: "var(--bw-success-color)",
10037
+ }, children: [voucher.type === "discount"
10038
+ ? `−${formatCurrency(voucher.discountAmount)} Rabatt`
10039
+ : `−${formatCurrency(voucher.balanceToUse || voucher.discountAmount)} Gutschein`, voucher.type === "giftCard" &&
10040
+ voucher.remainingBalance !== undefined &&
10041
+ voucher.remainingBalance > 0 && (u$2("span", { style: { color: "var(--bw-text-muted)", marginLeft: "8px" }, children: ["(Rest: ", formatCurrency(voucher.remainingBalance), ")"] }))] })] })] }), u$2("button", { type: "button", onClick: () => onRemoveVoucher(voucher.code), style: removeButtonStyle, title: "Entfernen", children: u$2(IconX, { size: 16 }) })] }, voucher.code))) })), u$2("div", { style: { display: "flex", gap: "8px" }, children: [u$2("input", { type: "text", value: inputValue, onChange: (e) => {
10042
+ setInputValue(e.target.value.toUpperCase());
10043
+ setError(null);
10044
+ }, onKeyDown: handleKeyDown, placeholder: hasDiscountCode
10045
+ ? "Gutscheincode eingeben..."
10046
+ : "Rabatt- oder Gutscheincode eingeben...", style: inputStyle, disabled: disabled || isLoading, onFocus: (e) => {
10047
+ e.target.style.borderColor = "var(--bw-highlight-color)";
10048
+ e.target.style.boxShadow = "0 0 0 2px var(--bw-highlight-color)33";
10049
+ }, onBlur: (e) => {
10050
+ e.target.style.borderColor = "var(--bw-border-color)";
10051
+ e.target.style.boxShadow = "none";
10052
+ } }), u$2("button", { type: "button", onClick: handleSubmit, style: buttonStyle, disabled: disabled || isLoading || !inputValue.trim(), children: isLoading ? (u$2(IconSpinner, { size: 16, color: "#fff" })) : (u$2(k$3, { children: [u$2(IconCheck, { size: 16 }), "Einl\u00F6sen"] })) })] }), error && (u$2("div", { style: {
10053
+ marginTop: "8px",
10054
+ padding: "8px 12px",
10055
+ backgroundColor: "var(--bw-error-color)15",
10056
+ border: "1px solid var(--bw-error-color)40",
10057
+ borderRadius: "var(--bw-border-radius)",
10058
+ color: "var(--bw-error-color)",
10059
+ fontSize: "var(--bw-font-size)",
10060
+ fontFamily: "var(--bw-font-family)",
10061
+ display: "flex",
10062
+ alignItems: "center",
10063
+ gap: "8px",
10064
+ }, children: [u$2(IconX, { size: 16 }), error] })), hasDiscountCode && (u$2("div", { style: {
10065
+ marginTop: "8px",
10066
+ fontSize: "12px",
10067
+ color: "var(--bw-text-muted)",
10068
+ fontFamily: "var(--bw-font-family)",
10069
+ }, children: "\uD83D\uDCA1 Es wurde bereits ein Rabattcode angewendet. Du kannst weitere Gutscheine hinzuf\u00FCgen." }))] })), u$2("style", { children: `
10070
+ @keyframes spin {
10071
+ from { transform: rotate(0deg); }
10072
+ to { transform: rotate(360deg); }
10073
+ }
10074
+ ` })] }));
10075
+ }
10076
+
9743
10077
  // Form schemas
9744
10078
  const participantSchema = objectType({
9745
10079
  name: stringType().min(1, "Name ist erforderlich"),
@@ -9759,6 +10093,10 @@
9759
10093
  const IconWarning = ({ size = 48, color = "var(--bw-error-color)" }) => (u$2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [u$2("circle", { cx: "12", cy: "12", r: "10" }), u$2("line", { x1: "12", y1: "8", x2: "12", y2: "12" }), u$2("circle", { cx: "12", cy: "16", r: "1" })] }));
9760
10094
  const IconMoney = ({ size = 20, color = "var(--bw-text-muted)" }) => (u$2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [u$2("rect", { x: "2", y: "6", width: "20", height: "12", rx: "2" }), u$2("circle", { cx: "12", cy: "12", r: "4" }), u$2("line", { x1: "2", y1: "10", x2: "2", y2: "14" }), u$2("line", { x1: "22", y1: "10", x2: "22", y2: "14" })] }));
9761
10095
  function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError, onBackToEventInstances, onBackToEventTypes, selectedEventType, selectedEventInstance, isOpen, onClose, systemConfig, }) {
10096
+ // New voucher system - supports multiple gift cards + one discount code
10097
+ const [appliedVouchers, setAppliedVouchers] = d$1([]);
10098
+ const [voucherError, setVoucherError] = d$1(null);
10099
+ // Legacy state for backward compatibility
9762
10100
  const [discountCode, setDiscountCode] = d$1(null);
9763
10101
  const [discountLoading, setDiscountLoading] = d$1(false);
9764
10102
  const [discountError, setDiscountError] = d$1(null);
@@ -9772,28 +10110,66 @@
9772
10110
  },
9773
10111
  });
9774
10112
  const watchedParticipants = form.watch("participants");
9775
- const watchedDiscountCode = form.watch("discountCode");
10113
+ form.watch("discountCode");
9776
10114
  const watchedCustomerName = form.watch("customerName");
9777
10115
  const watchedCustomerEmail = form.watch("customerEmail");
9778
10116
  const customerNameError = form.formState.errors.customerName;
9779
10117
  const customerEmailError = form.formState.errors.customerEmail;
9780
10118
  const watchedAcceptTerms = form.watch("acceptTerms");
9781
- // Calculate total amount and deposit amount
9782
- const calculateTotal = () => {
10119
+ // Calculate base total before any discounts
10120
+ const calculateBaseTotal = q$2(() => {
9783
10121
  if (!eventDetails)
9784
10122
  return 0;
9785
- const baseTotal = eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length;
9786
- return discountCode ? discountCode.newTotal : baseTotal;
9787
- };
10123
+ return eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length;
10124
+ }, [eventDetails, watchedParticipants]);
10125
+ // Calculate total discount from all applied vouchers
10126
+ const calculateTotalDiscount = q$2(() => {
10127
+ return appliedVouchers.reduce((total, voucher) => {
10128
+ if (voucher.type === "discount") {
10129
+ return total + voucher.discountAmount;
10130
+ }
10131
+ else if (voucher.type === "giftCard") {
10132
+ return total + (voucher.balanceToUse || voucher.discountAmount);
10133
+ }
10134
+ return total;
10135
+ }, 0);
10136
+ }, [appliedVouchers]);
10137
+ // Calculate total amount after discounts
10138
+ const calculateTotal = q$2(() => {
10139
+ const baseTotal = calculateBaseTotal();
10140
+ const totalDiscount = calculateTotalDiscount();
10141
+ return Math.max(0, baseTotal - totalDiscount);
10142
+ }, [calculateBaseTotal, calculateTotalDiscount]);
9788
10143
  const calculateDeposit = () => {
9789
10144
  if (!eventDetails || !eventDetails.deposit)
9790
10145
  return 0;
9791
10146
  const participantCount = watchedParticipants.filter((p) => p.name.trim()).length;
9792
10147
  return eventDetails.deposit * participantCount;
9793
10148
  };
10149
+ const baseTotal = calculateBaseTotal();
10150
+ const totalDiscount = calculateTotalDiscount();
9794
10151
  const totalAmount = calculateTotal();
9795
10152
  const depositAmount = calculateDeposit();
9796
- const paymentAmount = depositAmount > 0 ? depositAmount : totalAmount;
10153
+ // If there's a deposit, we pay the deposit; otherwise we pay the total after discounts
10154
+ const paymentAmount = depositAmount > 0 ? Math.max(0, depositAmount - totalDiscount) : totalAmount;
10155
+ // Get discount code for legacy compatibility
10156
+ const appliedDiscountCode = appliedVouchers.find((v) => v.type === "discount");
10157
+ // Get gift cards
10158
+ const appliedGiftCards = appliedVouchers.filter((v) => v.type === "giftCard");
10159
+ // Voucher handlers
10160
+ const handleVoucherValidated = q$2((voucher, error) => {
10161
+ if (error) {
10162
+ setVoucherError(error);
10163
+ return;
10164
+ }
10165
+ if (voucher) {
10166
+ setAppliedVouchers((prev) => [...prev, voucher]);
10167
+ setVoucherError(null);
10168
+ }
10169
+ }, []);
10170
+ const handleRemoveVoucher = q$2((code) => {
10171
+ setAppliedVouchers((prev) => prev.filter((v) => v.code !== code));
10172
+ }, []);
9797
10173
  // Form validation helper
9798
10174
  const isFormValid = () => {
9799
10175
  const participantCount = watchedParticipants.filter((p) => p.name.trim()).length;
@@ -9807,48 +10183,51 @@
9807
10183
  const hasAcceptedTerms = watchedAcceptTerms === true;
9808
10184
  return validParticipants && participantsWithinLimit && hasName && hasEmail && hasAcceptedTerms;
9809
10185
  };
9810
- // Validate discount codes
10186
+ // Re-validate vouchers when participant count changes (affects order value)
9811
10187
  y$1(() => {
9812
- const validateDiscountCode = async (code) => {
9813
- if (!code.trim() || !eventDetails) {
9814
- setDiscountCode(null);
9815
- setDiscountError(null);
9816
- return;
9817
- }
9818
- setDiscountLoading(true);
9819
- setDiscountError(null);
9820
- try {
9821
- const baseTotal = eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length;
9822
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/validate-discount"), {
9823
- method: "POST",
9824
- headers: createApiHeaders(config),
9825
- body: JSON.stringify(createRequestBody(config, {
9826
- code: code.trim(),
9827
- orderValue: baseTotal,
9828
- })),
9829
- });
9830
- const data = await response.json();
9831
- if (data.valid) {
9832
- setDiscountCode(data.discountCode);
10188
+ // When participants change, we need to recalculate voucher amounts
10189
+ // For now, we'll clear vouchers if the order value changes significantly
10190
+ // In a production app, you might want to re-validate each voucher
10191
+ if (appliedVouchers.length > 0) {
10192
+ // Recalculate discount amounts based on new order value
10193
+ const newBaseTotal = eventDetails?.price
10194
+ ? eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length
10195
+ : 0;
10196
+ // Update voucher amounts (simplified - in production, re-validate via API)
10197
+ setAppliedVouchers((prev) => prev.map((voucher) => {
10198
+ if (voucher.type === "discount") {
10199
+ let newDiscountAmount = 0;
10200
+ if (voucher.discountType === "percentage") {
10201
+ newDiscountAmount = Math.round((newBaseTotal * (voucher.discountValue || 0)) / 10000);
10202
+ }
10203
+ else {
10204
+ newDiscountAmount = voucher.discountValue || 0;
10205
+ }
10206
+ newDiscountAmount = Math.min(newDiscountAmount, newBaseTotal);
10207
+ return {
10208
+ ...voucher,
10209
+ discountAmount: newDiscountAmount,
10210
+ newTotal: newBaseTotal - newDiscountAmount,
10211
+ };
9833
10212
  }
9834
- else {
9835
- setDiscountCode(null);
9836
- setDiscountError(data.error);
10213
+ else if (voucher.type === "giftCard") {
10214
+ // Gift card balance stays the same, but amount to use might change
10215
+ const remainingAfterDiscount = newBaseTotal -
10216
+ prev
10217
+ .filter((v) => v.type === "discount")
10218
+ .reduce((sum, v) => sum + v.discountAmount, 0);
10219
+ const balanceToUse = Math.min(voucher.currentBalance || 0, Math.max(0, remainingAfterDiscount));
10220
+ return {
10221
+ ...voucher,
10222
+ balanceToUse,
10223
+ remainingBalance: (voucher.currentBalance || 0) - balanceToUse,
10224
+ discountAmount: balanceToUse,
10225
+ };
9837
10226
  }
9838
- }
9839
- catch (err) {
9840
- setDiscountError("Fehler beim Validieren des Rabattcodes");
9841
- setDiscountCode(null);
9842
- }
9843
- finally {
9844
- setDiscountLoading(false);
9845
- }
9846
- };
9847
- const timer = setTimeout(() => {
9848
- validateDiscountCode(watchedDiscountCode || "");
9849
- }, 500);
9850
- return () => clearTimeout(timer);
9851
- }, [watchedDiscountCode, watchedParticipants, eventDetails, config]);
10227
+ return voucher;
10228
+ }));
10229
+ }
10230
+ }, [watchedParticipants, eventDetails]);
9852
10231
  // Helper functions
9853
10232
  const addParticipant = () => {
9854
10233
  const currentParticipants = form.getValues("participants");
@@ -10003,7 +10382,7 @@
10003
10382
  color: "var(--bw-text-color)",
10004
10383
  fontWeight: "500",
10005
10384
  fontFamily: "var(--bw-font-family)",
10006
- }, children: [formatCurrency(eventDetails.price), " pro Person"] })] })] })] }), u$2("div", { style: { display: "flex", flexDirection: "column", gap: "var(--bw-spacing-large)" }, children: [u$2("form", { style: { display: "flex", flexDirection: "column", gap: "var(--bw-spacing-large)" }, children: [u$2("div", { style: {
10385
+ }, children: [formatCurrency(eventDetails.price), " pro Person"] })] })] })] }), u$2("div", { style: { display: "flex", flexDirection: "column", gap: "var(--bw-spacing-large)" }, children: [u$2("div", { style: { display: "flex", flexDirection: "column", gap: "var(--bw-spacing-large)" }, children: [u$2("div", { style: {
10007
10386
  backgroundColor: "var(--bw-surface-color)",
10008
10387
  border: `1px solid var(--bw-border-color)`,
10009
10388
  backdropFilter: "blur(4px)",
@@ -10148,7 +10527,7 @@
10148
10527
  color: "var(--bw-error-color)",
10149
10528
  fontSize: "var(--bw-font-size)",
10150
10529
  fontFamily: "var(--bw-font-family)",
10151
- }, children: ["Maximal ", eventDetails.availableSpots, " Pl\u00E4tze verf\u00FCgbar."] }))] })] }), u$2("div", { style: {
10530
+ }, children: ["Maximal ", eventDetails.availableSpots, " Pl\u00E4tze verf\u00FCgbar."] }))] })] }), u$2(VoucherInput, { config: config, orderValue: baseTotal, eventInstanceId: eventDetails?.id, customerEmail: watchedCustomerEmail, onVoucherValidated: handleVoucherValidated, appliedVouchers: appliedVouchers, onRemoveVoucher: handleRemoveVoucher, disabled: !eventDetails }), u$2("div", { style: {
10152
10531
  backgroundColor: "var(--bw-surface-color)",
10153
10532
  border: `1px solid var(--bw-border-color)`,
10154
10533
  backdropFilter: "blur(4px)",
@@ -10247,7 +10626,7 @@
10247
10626
  color: "var(--bw-text-color)",
10248
10627
  fontWeight: "500",
10249
10628
  fontFamily: "var(--bw-font-family)",
10250
- }, children: formatCurrency(eventDetails.deposit || 0) })] })), discountCode && (u$2(k$3, { children: [u$2("div", { style: {
10629
+ }, children: formatCurrency(eventDetails.deposit || 0) })] })), appliedVouchers.length > 0 && (u$2(k$3, { children: [u$2("div", { style: {
10251
10630
  display: "flex",
10252
10631
  justifyContent: "space-between",
10253
10632
  alignItems: "center",
@@ -10256,20 +10635,31 @@
10256
10635
  fontFamily: "var(--bw-font-family)",
10257
10636
  }, children: "Zwischensumme:" }), u$2("span", { style: {
10258
10637
  color: "var(--bw-text-muted)",
10259
- textDecoration: "line-through",
10638
+ textDecoration: totalDiscount > 0 ? "line-through" : "none",
10260
10639
  fontFamily: "var(--bw-font-family)",
10261
- }, children: formatCurrency(eventDetails.price *
10262
- watchedParticipants.filter((p) => p.name.trim()).length) })] }), u$2("div", { style: {
10640
+ }, children: formatCurrency(baseTotal) })] }), appliedDiscountCode && (u$2("div", { style: {
10263
10641
  display: "flex",
10264
10642
  justifyContent: "space-between",
10265
10643
  alignItems: "center",
10266
10644
  }, children: [u$2("span", { style: {
10267
10645
  color: "var(--bw-success-color)",
10268
10646
  fontFamily: "var(--bw-font-family)",
10269
- }, children: "Rabatt:" }), u$2("span", { style: {
10647
+ fontSize: "var(--bw-font-size)",
10648
+ }, children: ["Rabatt (", appliedDiscountCode.code, "):"] }), u$2("span", { style: {
10649
+ color: "var(--bw-success-color)",
10650
+ fontFamily: "var(--bw-font-family)",
10651
+ }, children: ["-", formatCurrency(appliedDiscountCode.discountAmount)] })] })), appliedGiftCards.map((giftCard) => (u$2("div", { style: {
10652
+ display: "flex",
10653
+ justifyContent: "space-between",
10654
+ alignItems: "center",
10655
+ }, children: [u$2("span", { style: {
10270
10656
  color: "var(--bw-success-color)",
10271
10657
  fontFamily: "var(--bw-font-family)",
10272
- }, children: ["-", formatCurrency(discountCode.discountAmount)] })] })] })), u$2("div", { style: {
10658
+ fontSize: "var(--bw-font-size)",
10659
+ }, children: ["Gutschein (", giftCard.code, "):"] }), u$2("span", { style: {
10660
+ color: "var(--bw-success-color)",
10661
+ fontFamily: "var(--bw-font-family)",
10662
+ }, children: ["-", formatCurrency(giftCard.balanceToUse || giftCard.discountAmount)] })] }, giftCard.code)))] })), u$2("div", { style: {
10273
10663
  borderTop: `1px solid var(--bw-border-color)`,
10274
10664
  paddingTop: "12px",
10275
10665
  }, children: [depositAmount > 0 && (u$2("div", { style: {
@@ -10342,7 +10732,15 @@
10342
10732
  fontFamily: "var(--bw-font-family)",
10343
10733
  borderBottom: "2px solid var(--bw-highlight-color)",
10344
10734
  paddingBottom: 4,
10345
- }, children: "Zahlung" }), u$2(PaymentForm, { config: config, eventDetails: eventDetails, formData: form.getValues(), totalAmount: paymentAmount, discountCode: discountCode, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, stripePromise: stripePromise, stripeAppearance: stripeAppearance })] }));
10735
+ }, children: "Zahlung" }), u$2(PaymentForm, { config: config, eventDetails: eventDetails, formData: form.getValues(), totalAmount: paymentAmount, discountCode: appliedDiscountCode ? {
10736
+ id: appliedDiscountCode.id,
10737
+ code: appliedDiscountCode.code,
10738
+ description: appliedDiscountCode.description || undefined,
10739
+ type: appliedDiscountCode.discountType || "percentage",
10740
+ value: appliedDiscountCode.discountValue || 0,
10741
+ discountAmount: appliedDiscountCode.discountAmount,
10742
+ newTotal: appliedDiscountCode.newTotal,
10743
+ } : null, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, stripePromise: stripePromise, stripeAppearance: stripeAppearance })] }));
10346
10744
  })()] })] }), u$2("style", { children: `
10347
10745
  .booking-widget-container *,
10348
10746
  .booking-widget-container *::before,
@@ -10993,16 +11391,7 @@
10993
11391
  } }) }));
10994
11392
  function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, }) {
10995
11393
  const [selectedEventInstanceId, setSelectedEventInstanceId] = d$1(null);
10996
- const [openMonths, setOpenMonths] = d$1(new Set());
10997
- const getEventsForMonth = (monthIndex) => {
10998
- return eventInstances.filter((instance) => new Date(instance.startTime).getMonth() === monthIndex);
10999
- };
11000
- const getMinPriceForMonth = (monthIndex) => {
11001
- const monthEvents = getEventsForMonth(monthIndex);
11002
- if (monthEvents.length === 0)
11003
- return 0;
11004
- return Math.min(...monthEvents.map((event) => event.price));
11005
- };
11394
+ const [openGroups, setOpenGroups] = d$1(new Set());
11006
11395
  const getMonthPriceDisplayInfo = (minPrice) => {
11007
11396
  return getPriceDisplayInfo(minPrice, yearPrices);
11008
11397
  };
@@ -11012,27 +11401,48 @@
11012
11401
  .filter((instance) => new Date(instance.startTime).getFullYear() === currentYear)
11013
11402
  .map((instance) => instance.price);
11014
11403
  const today = new Date();
11015
- // Get months that have events
11016
- const monthsWithEvents = months
11017
- .map((month, index) => ({
11018
- month,
11019
- index,
11020
- events: getEventsForMonth(index).sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime()),
11021
- minPrice: getMinPriceForMonth(index),
11022
- }))
11023
- .filter((monthData) => monthData.events.length > 0);
11404
+ // Group events by month and year, then sort chronologically (e.g., Aug 2025 before Apr 2026)
11405
+ const monthYearGroups = (() => {
11406
+ const groupsMap = new Map();
11407
+ for (const instance of eventInstances) {
11408
+ const date = new Date(instance.startTime);
11409
+ const year = date.getFullYear();
11410
+ const monthIndex = date.getMonth();
11411
+ const key = `${year}-${monthIndex}`;
11412
+ if (!groupsMap.has(key)) {
11413
+ groupsMap.set(key, {
11414
+ key,
11415
+ year,
11416
+ monthIndex,
11417
+ label: `${months[monthIndex]} ${year}`,
11418
+ events: [],
11419
+ minPrice: Number.POSITIVE_INFINITY,
11420
+ });
11421
+ }
11422
+ const group = groupsMap.get(key);
11423
+ group.events.push(instance);
11424
+ if (instance.price < group.minPrice)
11425
+ group.minPrice = instance.price;
11426
+ }
11427
+ return Array.from(groupsMap.values())
11428
+ .map((group) => ({
11429
+ ...group,
11430
+ events: group.events.sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime()),
11431
+ }))
11432
+ .sort((a, b) => (a.year === b.year ? a.monthIndex - b.monthIndex : a.year - b.year));
11433
+ })();
11024
11434
  const handleEventInstanceSelect = (eventInstance) => {
11025
11435
  setSelectedEventInstanceId(eventInstance.id);
11026
11436
  onEventInstanceSelect(eventInstance);
11027
11437
  };
11028
- const toggleMonth = (monthIndex) => {
11029
- if (openMonths.has(monthIndex)) {
11438
+ const toggleGroup = (key) => {
11439
+ if (openGroups.has(key)) {
11030
11440
  // Close if already open
11031
- setOpenMonths(new Set());
11441
+ setOpenGroups(new Set());
11032
11442
  }
11033
11443
  else {
11034
11444
  // Open only this one, close others
11035
- setOpenMonths(new Set([monthIndex]));
11445
+ setOpenGroups(new Set([key]));
11036
11446
  }
11037
11447
  };
11038
11448
  const handleClose = () => {
@@ -11289,10 +11699,7 @@
11289
11699
  minHeight: "400px",
11290
11700
  textAlign: "center",
11291
11701
  padding: "var(--bw-spacing)",
11292
- }, children: u$2("div", { children: [u$2("div", { style: {
11293
- margin: "0 auto 16px",
11294
- fontSize: "48px",
11295
- }, children: "\uD83D\uDCC5" }), u$2("h3", { style: {
11702
+ }, children: u$2("div", { children: [u$2("h3", { style: {
11296
11703
  marginBottom: "8px",
11297
11704
  fontWeight: "600",
11298
11705
  fontSize: "var(--bw-font-size-large)",
@@ -11360,136 +11767,154 @@
11360
11767
  font-size: 1.1rem !important;
11361
11768
  }
11362
11769
  }
11363
- ` }), u$2(Sidebar, { isOpen: isOpen, onClose: handleClose, title: `Terminauswahl - ${selectedEventType?.name || "Event"}`, children: u$2("div", { className: "bw-event-instance-list", style: { padding: "24px" }, children: u$2("div", { style: {
11770
+ ` }), u$2(Sidebar, { isOpen: isOpen, onClose: handleClose, title: `${selectedEventType?.name}`, children: u$2("div", { className: "bw-event-instance-list", style: { padding: "24px" }, children: u$2("div", { style: {
11364
11771
  display: "flex",
11365
11772
  flexDirection: "column",
11366
11773
  gap: "20px",
11367
- }, children: monthsWithEvents.map(({ month, index, events, minPrice }) => {
11774
+ }, children: monthYearGroups.map(({ key, label, events, minPrice, year }, idx) => {
11368
11775
  const monthPriceDisplayInfo = getMonthPriceDisplayInfo(minPrice);
11369
- return (u$2(Accordion, { title: month, priceInfo: u$2("div", { style: {
11370
- fontSize: "1rem",
11371
- backgroundColor: monthPriceDisplayInfo
11372
- ? monthPriceDisplayInfo.backgroundColor
11373
- : "#14532d",
11374
- color: monthPriceDisplayInfo ? monthPriceDisplayInfo.textColor : "#4ade80",
11375
- fontWeight: 500,
11376
- marginLeft: "auto",
11377
- padding: "4px 8px",
11378
- borderRadius: "var(--bw-border-radius-small)",
11379
- border: monthPriceDisplayInfo ? "none" : undefined,
11380
- boxShadow: monthPriceDisplayInfo
11381
- ? "0 2px 4px rgba(0, 0, 0, 0.2)"
11382
- : undefined,
11383
- }, children: `ab ${formatCurrency(minPrice)}` }), isOpen: openMonths.has(index), onToggle: () => toggleMonth(index), children: u$2("div", { style: {
11384
- display: "flex",
11385
- flexDirection: "column",
11386
- gap: "12px",
11387
- paddingTop: "12px",
11388
- }, children: events.map((event) => {
11389
- const availableSpots = event.maxParticipants - event.participantCount;
11390
- const isFullyBooked = availableSpots === 0;
11391
- const startDate = new Date(event.startTime);
11392
- const isPastEvent = today.toISOString() >= startDate.toISOString();
11393
- return (u$2("div", { className: "bw-event-instance-card", style: {
11394
- position: "relative",
11395
- cursor: !isFullyBooked && !isPastEvent && event.bookingOpen
11396
- ? "pointer"
11397
- : "not-allowed",
11398
- border: "1px solid var(--bw-border-color)",
11399
- backgroundColor: "var(--bw-surface-color)",
11400
- borderRadius: "var(--bw-border-radius)",
11401
- padding: "16px 20px",
11402
- transition: "all 0.2s ease",
11403
- opacity: isFullyBooked || isPastEvent ? 0.3 : 1,
11404
- filter: isFullyBooked || isPastEvent ? "grayscale(40%)" : "none",
11405
- fontFamily: "var(--bw-font-family)",
11406
- }, onClick: () => {
11407
- if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
11408
- handleEventInstanceSelect(event);
11409
- }
11410
- }, onMouseEnter: (e) => {
11411
- if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
11412
- e.currentTarget.style.transform = "scale(1.02)";
11413
- e.currentTarget.style.backgroundColor =
11414
- "var(--bw-surface-muted, rgba(59, 130, 246, 0.1))";
11415
- }
11416
- }, onMouseLeave: (e) => {
11417
- if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
11418
- e.currentTarget.style.transform = "scale(1)";
11419
- e.currentTarget.style.backgroundColor = "var(--bw-surface-color)";
11420
- }
11421
- }, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (u$2("div", { style: {
11422
- position: "absolute",
11423
- top: 0,
11424
- left: 0,
11425
- width: "100%",
11426
- height: "100%",
11427
- backgroundColor: "var(--bw-overlay-color, rgba(15, 23, 42, 0.8))",
11776
+ return (u$2(k$3, { children: [idx > 0 && monthYearGroups[idx - 1].year !== year && (u$2("div", { style: {
11777
+ height: 1,
11778
+ backgroundColor: "var(--bw-border-color)",
11779
+ margin: "4px 0",
11780
+ } })), u$2(Accordion, { title: label, priceInfo: u$2("div", { style: {
11781
+ fontSize: "1rem",
11782
+ backgroundColor: monthPriceDisplayInfo
11783
+ ? monthPriceDisplayInfo.backgroundColor
11784
+ : "#14532d",
11785
+ color: monthPriceDisplayInfo
11786
+ ? monthPriceDisplayInfo.textColor
11787
+ : "#4ade80",
11788
+ fontWeight: 500,
11789
+ marginLeft: "auto",
11790
+ padding: "4px 8px",
11791
+ borderRadius: "var(--bw-border-radius-small)",
11792
+ border: monthPriceDisplayInfo ? "none" : undefined,
11793
+ boxShadow: monthPriceDisplayInfo
11794
+ ? "0 2px 4px rgba(0, 0, 0, 0.2)"
11795
+ : undefined,
11796
+ }, children: `ab ${formatCurrency(minPrice)}` }), isOpen: openGroups.has(key), onToggle: () => toggleGroup(key), children: u$2("div", { style: {
11797
+ display: "flex",
11798
+ flexDirection: "column",
11799
+ gap: "12px",
11800
+ paddingTop: "12px",
11801
+ }, children: events.map((event) => {
11802
+ const availableSpots = event.maxParticipants - event.participantCount;
11803
+ const isFullyBooked = availableSpots === 0;
11804
+ const startDate = new Date(event.startTime);
11805
+ const isPastEvent = today.toISOString() >= startDate.toISOString();
11806
+ return (u$2("div", { className: "bw-event-instance-card", style: {
11807
+ position: "relative",
11808
+ cursor: !isFullyBooked && !isPastEvent && event.bookingOpen
11809
+ ? "pointer"
11810
+ : "not-allowed",
11811
+ border: "1px solid var(--bw-border-color)",
11812
+ backgroundColor: "var(--bw-surface-color)",
11428
11813
  borderRadius: "var(--bw-border-radius)",
11429
- display: "flex",
11430
- alignItems: "center",
11431
- justifyContent: "center",
11432
- }, children: u$2("div", { style: {
11433
- width: "32px",
11434
- height: "32px",
11435
- color: "var(--bw-highlight-color-muted, rgba(59, 130, 246, 0.8))",
11436
- animation: "spin 1s linear infinite",
11437
- fontSize: "32px",
11438
- }, children: spinner() }) })), u$2(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices }), u$2(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants }), u$2("div", { style: {
11439
- display: "flex",
11440
- justifyContent: "space-between",
11441
- width: "100%",
11442
- alignItems: "start",
11443
- gap: "12px",
11444
- marginBottom: "4px",
11445
- }, children: [u$2("div", { style: { display: "flex", alignItems: "start", gap: "12px" }, children: [u$2("div", { className: "bw-event-instance-datebox", style: {
11446
- fontSize: "var(--bw-font-size)",
11447
- transition: "all 0.2s ease",
11448
- borderRadius: "var(--bw-border-radius-small)",
11449
- borderTop: `4px solid var(--bw-border-color)`,
11450
- border: "1px solid var(--bw-border-color)",
11451
- width: "40px",
11452
- height: "40px",
11453
- display: "flex",
11454
- alignItems: "center",
11455
- justifyContent: "center",
11456
- fontWeight: "bold",
11457
- color: "var(--bw-text-color)",
11458
- backgroundColor: "var(--bw-background-color)",
11459
- }, children: startDate.getDate() }), u$2("div", { style: {
11460
- fontSize: "var(--bw-font-size)",
11461
- color: "var(--bw-text-color)",
11814
+ padding: "16px 20px",
11815
+ transition: "all 0.2s ease",
11816
+ opacity: isFullyBooked || isPastEvent ? 0.3 : 1,
11817
+ filter: isFullyBooked || isPastEvent ? "grayscale(40%)" : "none",
11818
+ fontFamily: "var(--bw-font-family)",
11819
+ }, onClick: () => {
11820
+ if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
11821
+ handleEventInstanceSelect(event);
11822
+ }
11823
+ }, onMouseEnter: (e) => {
11824
+ if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
11825
+ e.currentTarget.style.transform = "scale(1.02)";
11826
+ e.currentTarget.style.backgroundColor =
11827
+ "var(--bw-surface-muted, rgba(59, 130, 246, 0.1))";
11828
+ }
11829
+ }, onMouseLeave: (e) => {
11830
+ if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
11831
+ e.currentTarget.style.transform = "scale(1)";
11832
+ e.currentTarget.style.backgroundColor = "var(--bw-surface-color)";
11833
+ }
11834
+ }, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (u$2("div", { style: {
11835
+ position: "absolute",
11836
+ top: 0,
11837
+ left: 0,
11838
+ width: "100%",
11839
+ height: "100%",
11840
+ backgroundColor: "var(--bw-overlay-color, rgba(15, 23, 42, 0.8))",
11841
+ borderRadius: "var(--bw-border-radius)",
11842
+ display: "flex",
11843
+ alignItems: "center",
11844
+ justifyContent: "center",
11845
+ }, children: u$2("div", { style: {
11846
+ width: "32px",
11847
+ height: "32px",
11848
+ color: "var(--bw-highlight-color-muted, rgba(59, 130, 246, 0.8))",
11849
+ animation: "spin 1s linear infinite",
11850
+ fontSize: "32px",
11851
+ }, children: spinner() }) })), u$2(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices }), u$2(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants }), u$2("div", { style: {
11852
+ display: "flex",
11853
+ justifyContent: "space-between",
11854
+ width: "100%",
11855
+ alignItems: "start",
11856
+ gap: "12px",
11857
+ marginBottom: "4px",
11858
+ }, children: [u$2("div", { style: { display: "flex", alignItems: "start", gap: "12px" }, children: [u$2("div", { className: "bw-event-instance-datebox", style: {
11859
+ fontSize: "var(--bw-font-size)",
11860
+ transition: "all 0.2s ease",
11861
+ borderRadius: "var(--bw-border-radius-small)",
11862
+ borderTop: `4px solid var(--bw-border-color)`,
11863
+ border: "1px solid var(--bw-border-color)",
11864
+ width: "40px",
11865
+ height: "40px",
11866
+ display: "flex",
11867
+ alignItems: "center",
11868
+ justifyContent: "center",
11869
+ fontWeight: "bold",
11870
+ color: "var(--bw-text-color)",
11871
+ backgroundColor: "var(--bw-background-color)",
11872
+ }, children: startDate.getDate() }), u$2("div", { style: {
11873
+ fontSize: "var(--bw-font-size)",
11874
+ color: "var(--bw-text-color)",
11875
+ display: "flex",
11876
+ flexDirection: "column",
11877
+ alignItems: "start",
11878
+ justifyContent: "start",
11879
+ lineHeight: "1.2",
11880
+ }, children: [u$2("div", { children: [u$2("span", { className: "bw-event-instance-title", style: { fontWeight: "600", marginBottom: "2px" }, children: formatWeekday(event.startTime) }), formatWeekday(event.startTime) !==
11881
+ formatWeekday(event.endTime) && (u$2(k$3, { children: [u$2("span", { style: {
11882
+ color: "var(--bw-text-muted)",
11883
+ fontSize: "14px",
11884
+ }, children: " - " }), u$2("span", { className: "bw-event-instance-title", style: { fontWeight: "600", marginBottom: "2px" }, children: formatWeekday(event.endTime) })] }))] }), u$2("div", { children: formatWeekday(event.startTime) ===
11885
+ formatWeekday(event.endTime) ? (u$2(k$3, { children: [u$2("span", { style: {
11886
+ color: "var(--bw-text-muted)",
11887
+ fontSize: "14px",
11888
+ }, children: formatTime(event.startTime) }), u$2("span", { style: {
11889
+ color: "var(--bw-text-muted)",
11890
+ fontSize: "14px",
11891
+ }, children: " - " }), u$2("span", { style: {
11892
+ color: "var(--bw-text-muted)",
11893
+ fontSize: "14px",
11894
+ }, children: formatTime(event.endTime) })] })) : (u$2("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: [formatTime(event.startTime), " Uhr"] })) })] }), u$2("span", { style: {
11895
+ fontSize: "12px",
11896
+ fontWeight: 400,
11897
+ color: "var(--bw-text-muted)",
11898
+ marginLeft: "6px",
11899
+ background: "var(--bw-background-muted)",
11900
+ whiteSpace: "nowrap",
11901
+ }, children: [event.durationDays, " Tag", event.durationDays > 1 ? "e" : ""] })] }), u$2("div", { className: "bw-event-instance-price", style: {
11902
+ textAlign: "right",
11462
11903
  display: "flex",
11463
11904
  flexDirection: "column",
11464
- alignItems: "start",
11465
- justifyContent: "start",
11466
- lineHeight: "1.2",
11467
- }, children: [u$2("div", { children: [u$2("span", { className: "bw-event-instance-title", style: { fontWeight: "600", marginBottom: "2px" }, children: formatWeekday(event.startTime) }), formatWeekday(event.startTime) !==
11468
- formatWeekday(event.endTime) && (u$2(k$3, { children: [u$2("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), u$2("span", { className: "bw-event-instance-title", style: { fontWeight: "600", marginBottom: "2px" }, children: formatWeekday(event.endTime) })] }))] }), u$2("div", { children: formatWeekday(event.startTime) ===
11469
- formatWeekday(event.endTime) ? (u$2(k$3, { children: [u$2("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.startTime) }), u$2("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), u$2("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.endTime) })] })) : (u$2("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: [formatTime(event.startTime), " Uhr"] })) })] }), u$2("span", { style: {
11470
- fontSize: "12px",
11471
- fontWeight: 400,
11472
- color: "var(--bw-text-muted)",
11473
- marginLeft: "6px",
11474
- background: "var(--bw-background-muted)",
11475
- whiteSpace: "nowrap",
11476
- }, children: [event.durationDays, " Tag", event.durationDays > 1 ? "e" : ""] })] }), u$2("div", { className: "bw-event-instance-price", style: {
11477
- textAlign: "right",
11905
+ alignItems: "end",
11906
+ }, children: u$2(PriceDisplay, { price: event.price, yearPrices: yearPrices }) })] }), event.name !== selectedEventType?.name && (u$2("h4", { className: "bw-event-instance-title", style: {
11907
+ fontSize: "var(--bw-font-size)",
11908
+ fontWeight: "600",
11909
+ color: "var(--bw-text-color)",
11910
+ lineHeight: "1.25",
11911
+ margin: "0 0 2px 0",
11478
11912
  display: "flex",
11479
- flexDirection: "column",
11480
- alignItems: "end",
11481
- }, children: u$2(PriceDisplay, { price: event.price, yearPrices: yearPrices }) })] }), event.name !== selectedEventType?.name && (u$2("h4", { className: "bw-event-instance-title", style: {
11482
- fontSize: "var(--bw-font-size)",
11483
- fontWeight: "600",
11484
- color: "var(--bw-text-color)",
11485
- lineHeight: "1.25",
11486
- margin: "0 0 2px 0",
11487
- display: "flex",
11488
- alignItems: "center",
11489
- gap: "8px",
11490
- maxWidth: "230px",
11491
- }, children: event.name }))] }, event.id));
11492
- }) }) }, month));
11913
+ alignItems: "center",
11914
+ gap: "8px",
11915
+ maxWidth: "230px",
11916
+ }, children: event.name }))] }, event.id));
11917
+ }) }) })] }, key));
11493
11918
  }) }) }) })] }));
11494
11919
  }
11495
11920