diff --git a/src/server/app.js b/src/server/app.js index 25f4ead..95dae62 100644 --- a/src/server/app.js +++ b/src/server/app.js @@ -56,6 +56,8 @@ if ( settings.init ) { file = path.join( __dirname + '/../webapp/setup/dist/index.html' ); } +// TODO: Create plugin loader and manager + app.use( ( request, response ) => { response.sendFile( file ); diff --git a/src/server/backend/db/data/seatplan.json b/src/server/backend/db/data/seatplan.json index cb710e8..44c32ae 100644 --- a/src/server/backend/db/data/seatplan.json +++ b/src/server/backend/db/data/seatplan.json @@ -1 +1 @@ -{"test2":{"draft":{},"save":{"seatInfo":{"data":{"1":{"0":22},"2":{"0":9},"3":{"0":9}},"count":0},"data":{"1":{"x":299.02,"y":17.157,"h":564.951,"w":731.618,"active":false,"draggable":true,"resizable":true,"id":1,"origin":1,"shape":"rectangular","type":"seat","startingRow":1,"seatNumberingPosition":1,"sector":"A","text":{"text":"TestText","textSize":20,"colour":"#20FFFF"},"numberingDirection":"left","category":"1"},"2":{"x":359.069,"y":661.765,"h":121.324,"w":604.167,"active":false,"draggable":true,"resizable":true,"id":2,"origin":3,"shape":"rectangular","type":"stage","startingRow":1,"seatNumberingPosition":1,"sector":"A","text":{"text":"TestText","textSize":20,"colour":"#20FFFF"},"ticketCount":1,"numberingDirection":"left","category":"1"},"3":{"x":519.608,"y":671.569,"h":83.333,"w":306.373,"active":false,"draggable":true,"resizable":true,"id":3,"origin":1,"shape":"rectangular","type":"text","startingRow":1,"seatNumberingPosition":2,"sector":"A","text":{"text":"Stage","textSize":25,"colour":"#2160ff"},"ticketCount":1,"numberingDirection":"left","category":"1"}}}},"test":{"draft":{},"save":{"seatInfo":{"data":{"1":{"0":22}},"count":0},"data":{"1":{"x":122.549,"y":122.549,"h":371.324,"w":735.294,"active":false,"draggable":true,"resizable":true,"id":1,"origin":1,"shape":"rectangular","type":"seat","startingRow":1,"seatNumberingPosition":1,"sector":"A","text":{"text":"TestText","textSize":20,"colour":"#20FFFF"},"numberingDirection":"left","category":"1"}}}}} \ No newline at end of file +{"test2":{"draft":{},"save":{"seatInfo":{"data":{"1":{"0":22},"2":{"0":9},"3":{"0":9}},"count":0},"data":{"1":{"x":299.02,"y":17.157,"h":564.951,"w":731.618,"active":false,"draggable":true,"resizable":true,"id":1,"origin":1,"shape":"rectangular","type":"seat","startingRow":1,"seatNumberingPosition":1,"sector":"A","text":{"text":"TestText","textSize":20,"colour":"#20FFFF"},"numberingDirection":"left","category":"1"},"2":{"x":359.069,"y":661.765,"h":121.324,"w":604.167,"active":false,"draggable":true,"resizable":true,"id":2,"origin":3,"shape":"rectangular","type":"stage","startingRow":1,"seatNumberingPosition":1,"sector":"A","text":{"text":"TestText","textSize":20,"colour":"#20FFFF"},"ticketCount":1,"numberingDirection":"left","category":"1"},"3":{"x":519.608,"y":671.569,"h":83.333,"w":306.373,"active":false,"draggable":true,"resizable":true,"id":3,"origin":1,"shape":"rectangular","type":"text","startingRow":1,"seatNumberingPosition":2,"sector":"A","text":{"text":"Stage","textSize":25,"colour":"#2160ff"},"ticketCount":1,"numberingDirection":"left","category":"1"}}}},"test":{"draft":{},"save":{"seatInfo":{"data":{"1":{"0":22}},"count":0},"data":{"1":{"x":427.696,"y":160.539,"h":371.324,"w":734.069,"active":false,"draggable":true,"resizable":true,"id":1,"origin":1,"shape":"rectangular","type":"stand","startingRow":1,"seatNumberingPosition":1,"sector":"A","text":{"text":"TestText","textSize":20,"colour":"#20FFFF"},"numberingDirection":"left","category":"1","ticketCount":50}}}}} \ No newline at end of file diff --git a/src/webapp/main/index.html b/src/webapp/main/index.html index 5ce9aa3..11a4716 100644 --- a/src/webapp/main/index.html +++ b/src/webapp/main/index.html @@ -12,6 +12,7 @@ + diff --git a/src/webapp/main/src/App.vue b/src/webapp/main/src/App.vue index c6f446a..8fe1139 100644 --- a/src/webapp/main/src/App.vue +++ b/src/webapp/main/src/App.vue @@ -178,6 +178,11 @@ export default { } }, created () { + if ( window.webpage.engine.trident ) { + alert( 'Welcome! We have detected that you are still using Internet Explorer or a similar browser. As a modern webapp, libreevent does NOT officially support Internet Explorer. If you run into problems whilst using this webapp, please switch to a modern browser like Firefox.' ) + } else if ( window.webpage.engine.presto ) { + alert( 'Welcome! We have detected that you are a very old version of Opera or related browser. As a modern webapp, libreevent does only support modern browsers. If you run into issues whilst using this webapp, please switch to a modern browser, like Firefox.' ) + } this.theme = localStorage.getItem( 'theme' ) ? localStorage.getItem( 'theme' ) : ''; if ( window.matchMedia( '(prefers-color-scheme: dark)' ).matches || this.theme === '☼' ) { document.documentElement.classList.add( 'dark' ); diff --git a/src/webapp/main/src/components/noseatplan.vue b/src/webapp/main/src/components/noseatplan.vue index 8e489ea..48cc965 100644 --- a/src/webapp/main/src/components/noseatplan.vue +++ b/src/webapp/main/src/components/noseatplan.vue @@ -41,7 +41,7 @@ export default { data () { return { tickets: { 'ticket1': { 'name': 'Ticket 1', 'id': 'ticket1', 'category': 1, 'free': 20 }, 'ticket2': { 'name': 'Ticket 2', 'id': 'ticket2', 'category': 2, 'free': 20 } }, - event: { 'name': 'TestEvent', 'location': 'TestLocation', 'date': 'TestDate', 'RoomName': 'TestRoom', 'currency': 'CHF', 'categories': { '1': { 'price': { '1':25, '2':35 }, 'bg': 'black', 'fg': 'white', 'name': 'Category 1' }, '2': { 'price': { '1':15, '2':20 }, 'bg': 'green', 'fg': 'white', 'name': 'Category 2' } }, 'ageGroups': { '1':{ 'id': 1, 'name':'Child', 'age':'0 - 15.99 years' }, '2':{ 'id': 2, 'name': 'Adult', 'age': null } }, 'ageGroupCount':2, 'stage': true }, + event: { 'name': 'TestEvent', 'location': 'TestLocation', 'date': 'TestDate', 'RoomName': 'TestRoom', 'currency': 'CHF', 'categories': { '1': { 'price': { '1':25, '2':35 }, 'bg': 'black', 'fg': 'white', 'name': 'Category 1' }, '2': { 'price': { '1':15, '2':20 }, 'bg': 'green', 'fg': 'white', 'name': 'Category 2' } }, 'ageGroups': { '1':{ 'id': 1, 'name':'Child', 'age':'0 - 15.99 years' }, '2':{ 'id': 2, 'name': 'Adult', 'age': null } }, 'stage': true }, cart: {}, } }, diff --git a/src/webapp/main/src/components/notifications/popups.vue b/src/webapp/main/src/components/notifications/popups.vue index 0f1aa3e..eddc51b 100644 --- a/src/webapp/main/src/components/notifications/popups.vue +++ b/src/webapp/main/src/components/notifications/popups.vue @@ -42,6 +42,28 @@ +
+

{{ data.message }}

+ + + + + + +
+ {{ ticketOption.name }}
({{ ticketOption.age }})
+
+ {{ data.options.currency }} {{ data.options.price[ ticketOption.id ] }} + + add + {{ data.options.count[ ticketOption.id ] }} + remove +
+
+ + +
+
@@ -66,7 +88,7 @@ data () { return { contentType: 'dropdown', - data: {} + data: {}, } }, methods: { @@ -76,14 +98,32 @@ this.$emit( 'data', { 'data': this.data.selected, 'status': message } ); } }, + selectTicket ( option ) { + let total = 0; + for ( let i in this.data.options.count ) { + total += this.data.options.count[ i ]; + } + + if ( total < this.data.options.max ) { + this.data.options.count[ option ] += 1; + } + }, + deselectTicket ( option ) { + if ( this.data.options.count[ option ] > 0 ) { + this.data.options.count[ option ] -= 1; + } + }, + submitTicket () { + $( '#popup-backdrop' ).fadeOut( 300 ); + this.$emit( 'ticket', { 'data': this.data.options.count, 'component': this.data.options.id } ); + }, closePopupAdvanced ( message, data ) { this.data[ 'selected' ] = data; this.closePopup( message ); }, openPopup ( message, options, dataType, selected ) { - let data = { 'message': message ? message : 'No message defined on method call!!', 'options': options ? options : { '1': { 'value': 'undefined', 'displayName': 'No options specified in call' } }, 'selected': selected ? selected : '' }; - this.data = data; - this.contentType = dataType ? dataType : 'string'; + this.data = { 'message': message ?? 'No message defined on method call!!', 'options': options ?? { '1': { 'value': 'undefined', 'displayName': 'No options specified in call' } }, 'selected': selected ?? '' }; + this.contentType = dataType ?? 'string'; $( '#popup-backdrop' ).fadeIn( 300 ); } }, @@ -200,4 +240,13 @@ .select-button:hover { background-color: var( --hover-color ) !important; } + + .controls { + user-select: none; + cursor: pointer; + font-size: 100%; + font-weight: bold; + border: solid var( --primary-color ) 1px; + border-radius: 100%; + } \ No newline at end of file diff --git a/src/webapp/main/src/components/seatplan/editor/window.vue b/src/webapp/main/src/components/seatplan/editor/window.vue index 053b6bf..ad8622f 100644 --- a/src/webapp/main/src/components/seatplan/editor/window.vue +++ b/src/webapp/main/src/components/seatplan/editor/window.vue @@ -179,9 +179,14 @@ this.available.undo = true; } - let supportedBrowser = []; + if ( window.webpage.engine.trident ) { + alert( 'Welcome! We have detected that you are still using Internet Explorer or a similar browser. As a modern webapp, libreevent does NOT officially support Internet Explorer. If you run into problems whilst using this webapp, please switch to a modern browser like Firefox.' ); + } else if ( window.webpage.engine.presto ) { + alert( 'Welcome! We have detected that you are a very old version of Opera or related browser. As a modern webapp, libreevent does only support modern browsers. If you run into issues whilst using this webapp, please switch to a modern browser, like Firefox.' ); + } else if ( window.webpage.engine.webkit ) { + alert( 'Hello! Whilst tested with some versions of Webkit (the browser engine of Safari), support for this engine is still unofficial. Therefore we cannot guarantee that all the features of the seatplan editor function as they should. If you run into problems, please contact us through the link provided in the documentation.' ); + } this.save(); - // TODO: Add warning for untested browsers & suboptimal window sizes! }, eventHandler ( e ) { if ( this.prevSize.h != window.innerHeight || this.prevSize.w != window.innerWidth ) { diff --git a/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/standing.vue b/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/standing.vue index 756ff5e..6d1c332 100644 --- a/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/standing.vue +++ b/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/standing.vue @@ -94,7 +94,7 @@ export default { this.style = 'border-style: none none none solid'; this.circularStyle = 'top: -100%; right: 100%;'; } - } + }, }, watch: { origin ( value ) { diff --git a/src/webapp/main/src/components/seatplan/userApp/userWindow.vue b/src/webapp/main/src/components/seatplan/userApp/userWindow.vue index 24b9fd8..01b6dd0 100644 --- a/src/webapp/main/src/components/seatplan/userApp/userWindow.vue +++ b/src/webapp/main/src/components/seatplan/userApp/userWindow.vue @@ -31,7 +31,8 @@ @seatSelected="( seat ) => { seatSelected( seat ) }" @seatDeselected="( seat ) => { seatDeselected( seat ) }"> - + @@ -43,7 +44,8 @@ - + @@ -77,7 +79,7 @@ data() { return { draggables: { 1: { 'x': 100, 'y':100, 'h': 100, 'w': 250, 'active': false, 'draggable': true, 'resizable': true, 'id': 1, 'origin': 1, 'shape':'rectangular', 'type': 'seat', 'startingRow': 1, 'seatCountingStartingPoint': 1, 'sector': 'A', 'text': { 'text': 'TestText', 'textSize': 20, 'colour': '#20FFFF' }, 'ticketCount': 1, 'category': 1 } }, - event: { 'name': 'TestEvent2', 'location': 'TestLocation2', 'date': '2023-07-15', 'RoomName': 'TestRoom2', 'currency': 'CHF', 'categories': { '1': { 'price': { '1':25, '2':35 }, 'bg': 'black', 'fg': 'white', 'name': 'Category 1' }, '2': { 'price': { '1':15, '2':20 }, 'bg': 'green', 'fg': 'white', 'name': 'Category 2' } }, 'ageGroups': { '1':{ 'id': 1, 'name':'Child', 'age':'0 - 15.99' }, '2':{ 'id': 2, 'name': 'Adult' } }, 'ageGroupCount': 2, 'maxTickets': 2 }, + event: { 'name': 'TestEvent2', 'location': 'TestLocation2', 'date': '2023-07-15', 'RoomName': 'TestRoom2', 'currency': 'CHF', 'categories': { '1': { 'price': { '1':25, '2':35 }, 'bg': 'black', 'fg': 'white', 'name': 'Category 1' }, '2': { 'price': { '1':15, '2':20 }, 'bg': 'green', 'fg': 'white', 'name': 'Category 2' } }, 'ageGroups': { '1':{ 'id': 1, 'name':'Child', 'age':'0 - 15.99' }, '2':{ 'id': 2, 'name': 'Adult' } }, 'maxTickets': 2 }, available: { 'redo': false, 'undo': false }, scaleFactor: 1, sizePoll: null, @@ -133,7 +135,39 @@ } } ); - // TODO: remove scaleDown function again once backend is up + // Mark all selected seats + all unavailable seats + // { 'sector': 'A', 'sectorCount': 1, 'unavailableSeats': { 'secAr0s0': 'nav' }, 'categoryInfo': { 'pricing': { '1': { 'displayName': 'Adults - CHF 20.-', 'value': 'adult', 'price': 20 }, '2': { 'displayName': 'Child (0 - 15.99y) - CHF 15.-', 'value': 'child', 'price': 15 } } } } + let categoryDetails = {}; + for ( let category in this.event.categories ) { + categoryDetails[ category ] = {}; + for ( let group in this.event.ageGroups ) { + categoryDetails[ category ][ group ] = {}; + categoryDetails[ category ][ group ] = { 'displayName': this.event.ageGroups[ group ].name + ( this.event.ageGroups[ group ].age ? ' (' + this.event.ageGroups[ group ].age + ')' : '' ) + ' - ' + this.event.currency + ' ' + this.event.categories[ category ].price[ group ], 'value': group, 'price': this.event.categories[ category ].price[ group ] }; + } + } + + for ( let element in this.draggables ) { + this.draggables[ element ][ 'data' ] = { 'sector': this.draggables[ element ][ 'sector' ], 'unavailableSeats': {}, 'categoryInfo': { 'pricing': categoryDetails[ this.draggables[ element ][ 'category' ] ] } }; + } + + if ( this.cart[ this.event.name ] ) { + let tickets = this.cart[ this.event.name ][ 'tickets' ]; + for ( let ticket in tickets ) { + this.draggables[ tickets[ ticket ].comp ][ 'data' ][ 'unavailableSeats' ][ ticket ] = 'sel'; + } + } + + // TODO: Check if all seats are available + let allSeatsAvailable = true; + // Method: Server sends all user selected seats + all selected seats. If seat is in both + // then selected, if just in all selected, taken, else available. + + if ( !allSeatsAvailable ) { + setTimeout( () => { + self.$refs.popups.openPopup( 'We are sorry to tell you that since the last time the seat plan was refreshed, one or more of the seats you have selected has/have been taken.', {}, 'string' ); + }, 500 ); + } + // TODO: Optimise for odd screen sizes and aspect ratios and fucking webkit sessionStorage.setItem( 'seatplan', JSON.stringify( this.scaleDown( this.draggables ) ) ); }, @@ -207,40 +241,6 @@ if ( sessionStorage.getItem( 'seatplan' ) ) { this.draggables = this.scaleUp( JSON.parse( sessionStorage.getItem( 'seatplan' ) ) ); } - - // TODO: Check if all seats are available - let allSeatsAvailable = true; - // Method: Server sends all user selected seats + all selected seats. If seat is in both - // then selected, if just in all selected, taken, else available. - - let self = this; - if ( !allSeatsAvailable ) { - setTimeout( () => { - self.$refs.popups.openPopup( 'We are sorry to tell you that since the last time the seat plan was refreshed, one or more of the seats you have selected has/have been taken.', {}, 'string' ); - }, 500 ); - } - - // Mark all selected seats + all unavailable seats - // { 'sector': 'A', 'sectorCount': 1, 'unavailableSeats': { 'secAr0s0': 'nav' }, 'categoryInfo': { 'pricing': { '1': { 'displayName': 'Adults - CHF 20.-', 'value': 'adult', 'price': 20 }, '2': { 'displayName': 'Child (0 - 15.99y) - CHF 15.-', 'value': 'child', 'price': 15 } } } } - let categoryDetails = {}; - for ( let category in this.event.categories ) { - categoryDetails[ category ] = {}; - for ( let group in this.event.ageGroups ) { - categoryDetails[ category ][ group ] = {}; - categoryDetails[ category ][ group ] = { 'displayName': this.event.ageGroups[ group ].name + ( this.event.ageGroups[ group ].age ? ' (' + this.event.ageGroups[ group ].age + ')' : '' ) + ' - ' + this.event.currency + ' ' + this.event.categories[ category ].price[ group ], 'value': group, 'price': this.event.categories[ category ].price[ group ] }; - } - } - - for ( let element in this.draggables ) { - this.draggables[ element ][ 'data' ] = { 'sector': this.draggables[ element ][ 'sector' ], 'unavailableSeats': {}, 'categoryInfo': { 'pricing': categoryDetails[ this.draggables[ element ][ 'category' ] ] } }; - } - - if ( this.cart[ this.event.name ] ) { - let tickets = this.cart[ this.event.name ][ 'tickets' ]; - for ( let ticket in tickets ) { - this.draggables[ tickets[ ticket ].comp ][ 'data' ][ 'unavailableSeats' ][ ticket ] = 'sel'; - } - } }, scaleUp ( valueArray ) { const allowedAttributes = [ 'w', 'h', 'x', 'y' ]; @@ -274,7 +274,6 @@ }, seatSelected ( seat ) { this.selectedSeat = seat; - console.log( seat ); if ( Object.keys( seat.option ).length > 1 ) { this.$refs.popups.openPopup( 'Please choose a ticket option', seat.option, 'selection', 'adult' ); } else { @@ -283,7 +282,6 @@ }, cartHandling ( operation, data ) { if ( operation === 'select' ) { - console.log( this.selectedSeat.option ); if ( this.cart[ this.event.name ] ) { this.cart[ this.event.name ][ 'tickets' ][ this.selectedSeat.id ] = { 'displayName': this.selectedSeat.displayName, 'price': this.selectedSeat.option[ data ].price, 'id': this.selectedSeat.id, 'option': data, 'comp': this.selectedSeat.componentID }; } else { @@ -301,17 +299,40 @@ localStorage.setItem( 'cart', JSON.stringify( this.cart ) ); }, reserveTicket ( option ) { - console.log( this.selectedSeat.componentID ); if ( option.status == 'ok' ) { this.$refs[ 'component' + this.selectedSeat.componentID ][ 0 ].validateSeatSelection( this.selectedSeat, option.data ); this.cartHandling( 'select', option.data ); } - // TODO: Make call to server to reserve ticket when data is returned & save to localStorage array. + // TODO: Make call to server to reserve ticket when data is returned }, seatDeselected ( seat ) { this.selectedSeat = seat; this.cartHandling( 'deselect' ); - // TODO: Make call to server to deselect ticket & delete from localStorage array and delete eventArray if empty! + // TODO: Make call to server to deselect ticket + }, + standing ( id ) { + const d = this.draggables[ id ]; + const evG = this.event.ageGroups; + let count = {}; + for ( let ageGroup in evG ) { + if ( this.cart[ 'ticket' + id + '_' + ageGroup ] ) { + count[ ageGroup ] = this.cart[ 'ticket' + id + '_' + ageGroup ].count; + } else { + count[ ageGroup ] = 0; + } + } + this.$refs.popups.openPopup( 'Select tickets', { + 'id': id, + 'max': d.ticketCount, + 'name': 'Sector ' + d.sector + ' Ticket ' + id, + 'ageGroups': this.event.ageGroups, + 'currency': this.event.currency, + 'price': this.event.categories[ d.category ], + 'count': count + }, 'tickets' ); + }, + standingTicketHandling ( data ) { + console.log( data ); } }, created () { diff --git a/src/webapp/main/src/views/admin/events/EventsDetailsView.vue b/src/webapp/main/src/views/admin/events/EventsDetailsView.vue index 2e2d193..dbe1307 100644 --- a/src/webapp/main/src/views/admin/events/EventsDetailsView.vue +++ b/src/webapp/main/src/views/admin/events/EventsDetailsView.vue @@ -63,11 +63,11 @@

General Settings

-

Currency codes used must be valid ISO 4217 codes. Read more on this page of the documentation

+

Currency codes used must be valid ISO 4217 codes. Read more on this page of the documentation

-

Please read into the documentation of this section if you want to use the requirements. It requires specific syntax to work. See here for more information

+

Please read into the documentation of this section if you want to use the requirements. It requires specific syntax to work. See here for more information

@@ -104,6 +104,9 @@ import settings from '@/components/settings/settings.vue'; import notifications from '@/components/notifications/notifications.vue'; + // TODO: When loading data form server, also load categories of this seat plan + // and from it construct the array, if not set already. + export default { name: 'TicketsDetailsView', components: { @@ -186,6 +189,11 @@ }, } } + }, + methods: { + save () { + + } } }; diff --git a/src/webapp/main/src/views/purchasing/TicketsOrderingView.vue b/src/webapp/main/src/views/purchasing/TicketsOrderingView.vue index 526aa1c..784fd8e 100644 --- a/src/webapp/main/src/views/purchasing/TicketsOrderingView.vue +++ b/src/webapp/main/src/views/purchasing/TicketsOrderingView.vue @@ -42,9 +42,9 @@ this.$router.push( '/tickets' ); } this.eventID = sessionStorage.getItem( 'selectedTicket' ); - if ( this.eventID == 'test' ) { - this.hasSeatplan = false; - } + // if ( this.eventID == 'test' ) { + // this.hasSeatplan = false; + // } } };