diff --git a/client/package-lock.json b/client/package-lock.json index cd4ad44..5b92124 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -21,6 +21,7 @@ "browserify": "^17.0.0", "browserify-css": "^0.15.0", "envify": "^4.1.0", + "iconv-lite": "^0.6.3", "jquery": "^3.6.0", "jquery-ui": "^1.12.1", "tsify": "^5.0.2", @@ -1184,6 +1185,18 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -3562,6 +3575,15 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", diff --git a/client/package.json b/client/package.json index 5a56376..0f97dfa 100644 --- a/client/package.json +++ b/client/package.json @@ -27,6 +27,7 @@ "browserify": "^17.0.0", "browserify-css": "^0.15.0", "envify": "^4.1.0", + "iconv-lite": "^0.6.3", "jquery": "^3.6.0", "jquery-ui": "^1.12.1", "tsify": "^5.0.2", @@ -48,4 +49,4 @@ "level": 2 } } -} \ No newline at end of file +} diff --git a/client/src/css/inject.css b/client/src/css/inject.css index c6cb49b..50e3b80 100644 --- a/client/src/css/inject.css +++ b/client/src/css/inject.css @@ -1,6 +1,7 @@ #containerBookingBtn { display: none; margin: 0; + text-align: center; } #get_flash { @@ -28,15 +29,16 @@ } #openSeatmap { - padding: 1rem; - font-weight: bold; - color: #ffb201; - font-size: 1rem; + padding: 1rem !important; + /* font-weight: bold; */ + /* color: #ffb201; */ + /* font-size: 1rem; */ cursor: pointer; + border-radius: 5px; } #openSeatmap span { - font-size: .8rem; + font-size: 1.2rem; /* color: #5c5c5c; */ } diff --git a/client/src/css/seatmap.css b/client/src/css/seatmap.css index 380ef97..2cc63d9 100644 --- a/client/src/css/seatmap.css +++ b/client/src/css/seatmap.css @@ -142,7 +142,6 @@ select#dropdownLegend, select#dropdownSeatmap, .dropdownBuyerTypes { } #modalCartRow { - /* max-height: 40vw; */ overflow: auto; } @@ -290,4 +289,18 @@ span.trimChar { #bottomHTML h6, #bottomHTML a { color: #999; +} + +#modalCartImportantNote { + margin: 0 0 20px 0; + display: none; +} + +#modalCart-overlay .uabb-modal { + pointer-events: auto; + width: 100% !important; + height: 100%; + overflow-y: scroll; + top: 0 !important; + transform: none !important; } \ No newline at end of file diff --git a/client/src/modules/cart.ts b/client/src/modules/cart.ts index 03c23da..60b885d 100644 --- a/client/src/modules/cart.ts +++ b/client/src/modules/cart.ts @@ -3,19 +3,23 @@ import { config } from "./config"; import * as I from "../types/types"; import * as Events from "./events"; import * as CartButtons from "./cartButtons"; +import Utils from "./utils"; export function addItem(inSeatObj: I.JSCSelectedSeat): void { const color: string = `#${XMLHelper.getVenuePricescalePropertyByPricescaleID("color", inSeatObj.data.seatsObj.id[0])}`; - const category: string | undefined = XMLHelper.getVenuePricescalePropertyByPricescaleID("desc", inSeatObj.data.seatsObj.id[0]); + const category: string | undefined = Utils.encodeCP850DecodeUTF8( XMLHelper.getVenuePricescalePropertyByPricescaleID("desc", inSeatObj.data.seatsObj.id[0])! ); const seat: string = config.state.layoutRows[inSeatObj.id][5]; const row: string = config.state.layoutRows[inSeatObj.id][4]; const sectionID: string = config.state.layoutRows[inSeatObj.id][3]; const sectionDesc: string | undefined = XMLHelper.getSectionDescBySectionID(sectionID); - const seatStr: string | undefined = `${sectionDesc}
Reihe ${row} Platz ${seat}`; + const seatStr: string | undefined = Utils.encodeCP850DecodeUTF8( `${sectionDesc}
Reihe ${row} Platz ${seat}` ); const buyerTypes: I.TypeBuyerType = XMLHelper.getBuyerTypesByPricescaleID(inSeatObj.data.seatsObj.id[0]); const cartID: string = `cartItem-${inSeatObj.id}`; const dropdownBuyerTypesSelector: string = `#${cartID} .dropdownBuyerTypes`; + console.log(category); + console.log(seatStr); + appendHTML(cartID, color, category, seatStr); addDropdownBuyerTypeOptions(buyerTypes, dropdownBuyerTypesSelector); Events.addCartDropdownBuyerTypes(dropdownBuyerTypesSelector, inSeatObj); @@ -44,17 +48,15 @@ export function changedDropdownBuyerType(inSelect: HTMLSelectElement, inSeatObj: Events.addRedirectCheckout(url); } -export function calcOverallPrice(): string | undefined { - if (!config.state.selectedSeatsArr.length) { +export function calcOverallPrice(): void{ + if (!config.state.selectedSeatsArr.length) config.state.priceOverall = "0"; - return "0"; - } + else + config.state.priceOverall = sumSeatPrices(); - config.state.priceOverall = sumSeatPrices(); - return config.state.priceOverall; + config.state.priceOverallEur = getPriceInEur(config.state.priceOverall); } - export function generateCartItems(): void { if (!config.state.selectedSeatsArr.length) return; @@ -82,6 +84,20 @@ export function generateCheckoutUrl(): string | undefined { return `${inputsWithValue["ticketPurchaseUrl"]}?user_context=${inputsWithValue.user_context}&pid=${inputsWithValue["pid"]}&selected_seat_indexes=${selectedSeatIndexes}&trxstate=148`; } +export function getPriceInEur(inPrice: string): string { + const price: number = parseInt(inPrice); + return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(price); +} + +export function setImportantNote(): void { + if (!config.state.inputsWithValue?.importantNote) + return; + + const importantNoteSelector: JQuery = jQuery("#modalCartImportantNote") + importantNoteSelector[0].innerText = config.state.inputsWithValue?.importantNote + importantNoteSelector.show(); +} + function appendHTML(inCartID: string, inColor: string, inCategory: string | undefined, inSeatStr: string): void { jQuery("#cartItemHTML .fl-html").append(`
@@ -104,10 +120,16 @@ function addDropdownBuyerTypeOptions(inBuyerTypes: I.TypeBuyerType, inSelector: } function appendOption(inSelector: string, inArr: I.TypeBuyerTypeArr): void { + const desc: string = Utils.encodeCP850DecodeUTF8( inArr[2] ); + const id: string = inArr[0]; + const price: string = getPriceInEur(inArr[1]); const dropdownBuyerTypes: HTMLElement = jQuery(inSelector).get(0); + + console.log(price); + let opt: HTMLOptionElement = document.createElement('option'); - opt.value = inArr[0]; - opt.innerHTML = `${inArr[2]} €${inArr[1]}`; + opt.value = id; + opt.innerHTML = `${desc} ${price}`; dropdownBuyerTypes.appendChild(opt); } diff --git a/client/src/modules/cartButtons.ts b/client/src/modules/cartButtons.ts index fd42188..5662130 100644 --- a/client/src/modules/cartButtons.ts +++ b/client/src/modules/cartButtons.ts @@ -7,20 +7,18 @@ export function setBtnCartText(): void { function createCartBtnText(): string { const numTickets: number = config.state.selectedSeatsArr.length; - - if (config.state.priceOverall !== "") - return numTickets === 1 ? `${numTickets} Ticket für €${config.state.priceOverall}` : `${numTickets} Tickets für €${config.state.priceOverall}`; - else - return "0 Tickets für €0.00"; + return numTickets === 1 ? `${numTickets} Ticket für ${config.state.priceOverallEur}` : `${numTickets} Tickets für ${config.state.priceOverallEur}`; } function createModalCartBtnText(): string { const numTickets: number = config.state.selectedSeatsArr.length; - if (config.state.priceOverall !== "") - return `Summe (${numTickets} Plätze) €${config.state.priceOverall}`; - else - return `Summe (0 Plätze) €0,00`; + return `Summe (${numTickets} Plätze) ${config.state.priceOverallEur}`; + + // if (config.state.priceOverall !== "") + // return `Summe (${numTickets} Plätze) €${config.state.priceOverall}`; + // else + // return `Summe (0 Plätze) €0,00`; } export function showHideBtnCartLoading(inSwitch: string): void { diff --git a/client/src/modules/child.ts b/client/src/modules/child.ts index 3a9c353..bda2030 100644 --- a/client/src/modules/child.ts +++ b/client/src/modules/child.ts @@ -52,23 +52,23 @@ export function initSendInputsWithValue(inE: any) { Communication.sendEventToParent("child_init_needVenueXML"); } - export function sendSeatmapXML(inE: any) { const data: I.Message = JSON.parse(inE.data); config.state.seatmapXML = data.message.map_response; const map: string[] = JSC.generateMap(); const rowsNaming: string[] = JSC.getRowsNaming(); const seats: I.JSCSeats = JSC.getSeats(); - const legend: I.JSCLegend = JSC.generateLegend("#JSCLegendInner"); + const legend: I.JSCLegend = Legend.generateLegend("#JSCLegendInner"); JSC.addSeatmap("#containerSeatmapInner", map, rowsNaming, seats, legend); - JSC.setUnavailableSeats(); + JSC.setBookedSeatsUnavailable(); JSC.selectSeatsInCart(); Legend.convertLegendToDropdown("dropdownLegend"); Events.dropdownLegendOnChange("#dropdownLegend"); Trims.addTrims(); XMLHelper.processSMAP(); config.state.panzoom = Panzoom.addPanzoom("#containerSeatmapInner", ".panzoomZoomIn", ".panzoomZoomOut", "#panzoomResetZoom"); + Cart.setImportantNote(); UI.controlLoftloader("hide"); jBoxHelper.createSeatTooltips(); } @@ -80,7 +80,7 @@ export function sendCheckoutResponse(inE: any) { Utils.consoleLog(data.message); if (!config.state.isValidSeatSelection) { - jBoxHelper.showJBoxNotice(`Auswahl nicht möglich: Bitte lassen Sie keinen einzelnen Platz frei.`); + jBoxHelper.showJBoxNotice(`Auswahl nicht möglich: Einzelplatz freigelassen oder max. Ticketsanzahl überschritten.`); CartButtons.showHideBtnCartLoading("hide"); } else { diff --git a/client/src/modules/config.ts b/client/src/modules/config.ts index 3c67331..1fbaab8 100644 --- a/client/src/modules/config.ts +++ b/client/src/modules/config.ts @@ -28,6 +28,7 @@ export const config: I.Config = { }, state: { priceOverall: "", + priceOverallEur: "", cartChanged: false, selectedSeatsArr: [], selectedSeatsObj: {}, diff --git a/client/src/modules/events.ts b/client/src/modules/events.ts index 5964092..e909fca 100644 --- a/client/src/modules/events.ts +++ b/client/src/modules/events.ts @@ -2,7 +2,6 @@ import * as Communication from "./communication"; import * as UI from "./ui"; import { config } from "./config"; import * as I from "../types/types"; -import * as JSC from "./jsc"; import * as Cart from "./cart"; import * as Legend from "./legend"; import * as jBoxHelper from "./jBoxHelper"; @@ -37,7 +36,7 @@ export function addModalCart(): void { else if (!config.state.cartChanged && config.state.isValidSeatSelection) UI.fadeInCartModal(); else if (!config.state.cartChanged && !config.state.isValidSeatSelection) - jBoxHelper.showJBoxNotice(`Auswahl nicht möglich: Bitte lassen Sie keinen einzelnen Platz frei.`); + jBoxHelper.showJBoxNotice(`Auswahl nicht möglich: Einzelplatz freigelassen oder max. Ticketsanzahl überschritten.`); // todo: outsource error messages }); } } @@ -75,29 +74,15 @@ export function addCartBack(): void { } export function dropdownLegendOnChange(inSelector: string): void { - const seatmap: any = config.state.seatmap; const dropdownLegend: HTMLElement = jQuery(inSelector).get(0); dropdownLegend.addEventListener("change", function (this: HTMLSelectElement) { - const value: string = this.value; - const className: string = `._${value}`; - - Legend.changeDropdownLegendBGColor(inSelector, value, className); - - if (value === "all") { - seatmap.find('unavailable').status('available'); - JSC.setUnavailableSeats(); - } - else { - seatmap.find('available').status('unavailable'); - JSC.activateSeatsBySectionID(value); - JSC.setUnavailableSeats(); - } + Legend.dropdownLegendOnChange(this, inSelector); }); } export function addCartDropdownBuyerTypes(inSelector: string, inSeatObj: I.JSCSelectedSeat): void { - // todo: which variant is not deprecated? + // todo: which jquery variant is not deprecated? jQuery(inSelector).on('change', function (this: HTMLSelectElement) { Cart.changedDropdownBuyerType(this, inSeatObj); }); diff --git a/client/src/modules/jBoxHelper.ts b/client/src/modules/jBoxHelper.ts index bcccea2..dfba175 100644 --- a/client/src/modules/jBoxHelper.ts +++ b/client/src/modules/jBoxHelper.ts @@ -1,6 +1,7 @@ import jBox from "jbox"; import { config } from "./config"; import * as XMLHelper from "./xmlhelper"; +import Utils from "./utils"; export function showJBoxNotice(inContent: string, inAutoClose: number | boolean | undefined = 5000): void { new jBox<"Notice"> ('Notice', { @@ -37,7 +38,7 @@ function showSeatTooltip(jBox: any): void { const seat: string = config.state.layoutRows[seatID][5]; const row: string = config.state.layoutRows[seatID][4]; const sectionID: string = config.state.layoutRows[seatID][3]; - const sectionDesc: string | undefined = XMLHelper.getSectionDescBySectionID(sectionID); + const sectionDesc: string | undefined = Utils.encodeCP850DecodeUTF8( XMLHelper.getSectionDescBySectionID(sectionID)! ); const tooltipContent: string = `${sectionDesc}
Reihe ${row} Platz ${seat}`; jBox.setContent(tooltipContent); diff --git a/client/src/modules/jsc.ts b/client/src/modules/jsc.ts index 0d6990c..c4a4e1a 100644 --- a/client/src/modules/jsc.ts +++ b/client/src/modules/jsc.ts @@ -22,16 +22,41 @@ export function getSeats(): I.JSCSeats { return seatmapInitSeats; } -export function activateSeatsBySectionID(inValue: string): void { +function getSeatAvailability(): I.SeatAvailability { + const seatmapXML: any = config.state.seatmapXML; + const availabilityArray: I.JSCAvailability = seatmapXML.seatmap[0].view_modes[0].view_mode[0].availability[0]; + const availableArray: string[] = availabilityArray.available_selectable_mask[0].split(","); + const unavailableArray: string[] = availabilityArray.unavailable_unselectable_mask[0].split(","); + + return { + available: availableArray, + unavailable: unavailableArray + } +} + +export function activateSeatsBySectionID(inSectionID: string): void { const seatmapXML: any = config.state.seatmapXML; - const seatmap: any = config.state.seatmap; const pricescaleArr: I.SeatmapPricescale[] = seatmapXML.seatmap[0].pricescale_config[0].pricescale; - const seatsArr: string[] | undefined = pricescaleArr.find(arr => { - return arr.id[0] === inValue; + const seatsArray: string[] | undefined = pricescaleArr.find(arr => { + return arr.id[0] === inSectionID; })?.mask[0].split(","); - seatmap.status(seatsArr, "available"); + if (seatsArray) + setSeatsAvailable(seatsArray) +} + +function setSeatsAvailable(inSeatsArray: string[]) { + const seatmap: any = config.state.seatmap; + const seatAvailability: I.SeatAvailability = getSeatAvailability(); + + inSeatsArray.forEach(seatID => { + // skip blacked out seats + if (!seatAvailability.available.includes(seatID) && !seatAvailability.unavailable.includes(seatID)) + return; + + seatmap.status(seatID, "available"); + }); } export function getRowsNaming(): string[] { @@ -57,7 +82,6 @@ export function generateMap(): string[] { const layout: I.SeatmapLayout = seatmapXML.seatmap[0].layouts[0].layout[0]; const rows: I.LayoutRow2[] = layout.rows[0].row; const pricescaleArr: I.SeatmapPricescale[] = seatmapXML.seatmap[0].pricescale_config[0].pricescale; - const availabilityArr: I.JSCAvailability = seatmapXML.seatmap[0].view_modes[0].view_mode[0].availability[0]; // Form: arrMatrix[Y][X] let arrMatrix: string[][] = createArrMatrix( @@ -66,25 +90,11 @@ export function generateMap(): string[] { "_" ); - arrMatrix = enterSeatsInMatrix(rows, arrMatrix, pricescaleArr, availabilityArr); + arrMatrix = enterSeatsInMatrix(rows, arrMatrix, pricescaleArr); return arrMatrix.map(element => element.join("")); } -export function generateLegend(inNode: string): I.JSCLegend { - const seatmapXML: any = config.state.seatmapXML; - const venueXML: I.VenueXML = config.state.inVenueXML!; - const pricescaleArr: I.SeatmapPricescale[] = seatmapXML.seatmap[0].pricescale_config[0].pricescale; - const venuePricescaleArr: I.Pricescale2[] = venueXML.venue[0].pricescales[0].pricescale; - - return { - node: jQuery(inNode), - items: createLegendItems(pricescaleArr, venuePricescaleArr) - } -} - - - -export function setUnavailableSeats(): void { +export function setBookedSeatsUnavailable(): void { const seatmapXML: any = config.state.seatmapXML; const seatmap: any = config.state.seatmap; const availabilityArr: I.JSCAvailability = seatmapXML.seatmap[0].view_modes[0].view_mode[0].availability[0]; @@ -140,19 +150,8 @@ export function addSeatmap(inSelector: string, inMap: string[], inRowsNaming: st }); } -function createLegendItems(pricescaleArr: I.SeatmapPricescale[], venuePricescaleArr: I.Pricescale2[]): string[][] { - return pricescaleArr.map((arr, index: number) => { - const id: string = arr.id[0]; - const seatsKey: string = String.fromCharCode(97 + index).toLocaleUpperCase(); - const desc: string | undefined = venuePricescaleArr.find(obj => obj.id[0] === id)?.desc[0]; - const description: string = `${desc} €${pricescaleArr[index].ref_price[0]}`; - return [seatsKey, "available", description]; - }); -} - -function enterSeatsInMatrix(inRows: I.LayoutRow2[], inArrMatrix: string[][], inPricescaleArr: I.SeatmapPricescale[], inAvailabilityArr: I.JSCAvailability): string[][] { - const availableArr: string[] = inAvailabilityArr.available_selectable_mask[0].split(","); - const unavailableArr: string[] = inAvailabilityArr.unavailable_unselectable_mask[0].split(","); +function enterSeatsInMatrix(inRows: I.LayoutRow2[], inArrMatrix: string[][], inPricescaleArr: I.SeatmapPricescale[]): string[][] { + const seatAvailability: I.SeatAvailability = getSeatAvailability(); inRows.forEach(element => { const row: I.LayoutRow2 = element; @@ -173,18 +172,19 @@ function enterSeatsInMatrix(inRows: I.LayoutRow2[], inArrMatrix: string[][], inP // 4: "13" -> Reihenbezeichnung // 5: "4" -> Platznummer - // skip blacked out seats - if (!availableArr.includes(seatArr[0]) && !unavailableArr.includes(seatArr[0])) + const seatID: string = seatArr[0]; + const seatXCoord: string = seatArr[2]; + + // skip blacked out seats + if (!seatAvailability.available.includes(seatID) && !seatAvailability.unavailable.includes(seatID)) return; - // const X: number = parseInt(seatArr[5]); - const X: number = parseInt(seatArr[2]) - 1; - const seatsKey: string | undefined = getSeatsKey(seatArr[0], inPricescaleArr); + const X: number = parseInt(seatXCoord) - 1; + const seatsKey: string | undefined = getSeatsKey(seatID, inPricescaleArr); if (seatsKey) inArrMatrix[Y][X] = `${seatsKey}[${seatArr[0]}, ]`; - // save seatArr in state with seatID as key config.state.layoutRows[seatArr[0]] = seatArr; }); diff --git a/client/src/modules/legend.ts b/client/src/modules/legend.ts index 0b1b1d5..fcc12d9 100644 --- a/client/src/modules/legend.ts +++ b/client/src/modules/legend.ts @@ -1,3 +1,9 @@ +import { config } from "./config"; +import * as I from "../types/types"; +import Utils from "./utils"; +import * as Cart from "./cart"; +import * as JSC from "./jsc"; + export function convertLegendToDropdown(inID: string): void { jQuery('ul.seatCharts-legendList').each(function () { let select: JQuery = jQuery(document.createElement('select')) @@ -7,34 +13,45 @@ export function convertLegendToDropdown(inID: string): void { select.attr({ id: inID }); appendFirstLegendOption(select); - appendLegendOptions(this, select); + appendLegendOptions(this, select); }); } -function appendLegendOptions(element: HTMLElement, inSelect: JQuery) { - jQuery('>li', element).each(function () { - const className: string = jQuery(this)[0].children[0].classList[3]; - const val: string = className.substring(1); - const text: string = (jQuery(this)[0].children[1]).innerText; +export function generateLegend(inNode: string): I.JSCLegend { + const seatmapXML: any = config.state.seatmapXML; + const venueXML: I.VenueXML = config.state.inVenueXML!; + const pricescaleArr: I.SeatmapPricescale[] = seatmapXML.seatmap[0].pricescale_config[0].pricescale; + const venuePricescaleArr: I.Pricescale2[] = venueXML.venue[0].pricescales[0].pricescale; - appendLegendOption(inSelect, className, val, text); - }); + return { + node: jQuery(inNode), + items: createLegendItems(pricescaleArr, venuePricescaleArr) + } } -function appendLegendOption(inSelect: JQuery, className: string, val: string, text: string): void { - let option: JQuery = jQuery('