diff --git a/src/server/admin/adminAPIRoutes.js b/src/server/admin/adminAPIRoutes.js new file mode 100644 index 0000000..ba4154c --- /dev/null +++ b/src/server/admin/adminAPIRoutes.js @@ -0,0 +1,43 @@ +/* +* libreevent - adminAPIRoutes.js +* +* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License +* https://janishutz.com, development@janishutz.com +* +* +*/ + +const posth = require( './api/postHandler.js' ); +const geth = require( './api/getHandler.js' ); +const postHandler = new posth(); +const getHandler = new geth(); +const path = require( 'path' ); + +// settings is missing in arguments which shouldn't pose any problem +module.exports = ( app ) => { + // Add specific routes here to have them be checked first to not get general handling + + app.get( '/admin/getAPI/:call', ( req, res ) => { + if ( req.session.loggedInAdmin ) { + getHandler.handleCall( req.params.call, req.query ).then( data => { + res.send( data ); + } ).catch( error => { + res.status( 500 ).send( error ); + } ); + } else { + res.status( 403 ).sendFile( path.join( __dirname + '/../ui/' + ( req.query.lang ?? 'en' ) + '/errors/403.html' ) ); + } + } ); + + app.post( '/admin/API/:call', ( req, res ) => { + if ( req.session.loggedInAdmin ) { + postHandler.handleCall( req.params.call, req.body, req.query.lang ).then( data => { + res.send( data ); + } ).catch( error => { + res.status( 500 ).send( error ); + } ); + } else { + res.status( 403 ).sendFile( path.join( __dirname + '/../ui/' + ( req.query.lang ?? 'en' ) + '/errors/403.html' ) ); + } + } ); +}; \ No newline at end of file diff --git a/src/server/admin/api/getHandler.js b/src/server/admin/api/getHandler.js new file mode 100644 index 0000000..33f6b0c --- /dev/null +++ b/src/server/admin/api/getHandler.js @@ -0,0 +1,48 @@ +/* +* libreevent - getHandler.js +* +* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License +* https://janishutz.com, development@janishutz.com +* +* +*/ + +const db = require( '../../backend/db/db.js' ); + +class GETHandler { + constructor () { + + } + + handleCall ( call, query ) { + return new Promise( ( resolve, reject ) => { + if ( call === 'getSeatplan' ) { + db.getJSONDataSimple( 'seatplan', query.location ).then( data => { + if ( Object.keys( data ).length > 0 ) { + resolve( data[ 'save' ] ); + } else { + reject( 'No data found for this location' ); + } + } ).catch( error => { + reject( error ); + } ); + } else if ( call === 'getSeatplanDraft' ) { + db.getJSONDataSimple( 'seatplan', query.location ).then( data => { + if ( Object.keys( data ).length > 0 ) { + if ( Object.keys( data[ 'draft' ] ).length > 0 ) { + resolve( data[ 'draft' ] ); + } else { + resolve( data[ 'save' ] ); + } + } else { + reject( 'No data found for this location' ); + } + } ).catch( error => { + reject( error ); + } ); + } + } ); + } +} + +module.exports = GETHandler; \ No newline at end of file diff --git a/src/server/admin/api/postHandler.js b/src/server/admin/api/postHandler.js new file mode 100644 index 0000000..94da952 --- /dev/null +++ b/src/server/admin/api/postHandler.js @@ -0,0 +1,41 @@ +/* +* libreevent - postHandler.js +* +* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License +* https://janishutz.com, development@janishutz.com +* +* +*/ + +const db = require( '../../backend/db/db.js' ); + +class POSTHandler { + constructor () { + + } + + handleCall ( call, data, lang ) { + return new Promise( ( resolve, reject ) => { + console.log( lang ); + if ( call === 'saveSeatplanDraft' ) { + db.getJSONDataSimple( 'seatplan', data.location ).then( res => { + let dat = res; + dat[ 'draft' ] = data.data; + db.writeJSONDataSimple( 'seatplan', data.location, dat ).then( resp => { + resolve( resp ); + } ).catch( error => { + reject( error ); + } ); + } ); + } else if ( call === 'saveSeatplan' ) { + db.writeJSONDataSimple( 'seatplan', data.location, { 'draft': {}, 'save': data.data } ).then( resp => { + resolve( resp ); + } ).catch( error => { + reject( error ); + } ); + } + } ); + } +} + +module.exports = POSTHandler; \ No newline at end of file diff --git a/src/server/app.js b/src/server/app.js index 089dce3..25f4ead 100644 --- a/src/server/app.js +++ b/src/server/app.js @@ -48,6 +48,8 @@ let file = path.join( __dirname + '/../webapp/main/dist/index.html' ); if ( settings.init ) { require( './admin/adminRoutes.js' )( app, settings ); // admin routes + require( './admin/adminAPIRoutes.js' )( app, settings ); // admin api routes + require( './backend/userAPIRoutes.js' )( app, settings ); // admin api routes require( './backend/userRoutes.js' )( app, settings ); // user routes } else { require( './setup/setupRoutes.js' )( app, settings ); // setup routes diff --git a/src/server/backend/api/getHandler.js b/src/server/backend/api/getHandler.js new file mode 100644 index 0000000..b652907 --- /dev/null +++ b/src/server/backend/api/getHandler.js @@ -0,0 +1,34 @@ +/* +* libreevent - getHandler.js +* +* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License +* https://janishutz.com, development@janishutz.com +* +* +*/ + +const db = require( '../db/db.js' ); + +class GETHandler { + constructor () { + + } + + handleCall ( call, query ) { + return new Promise( ( resolve, reject ) => { + if ( call === 'getSeatplan' ) { + db.getJSONDataSimple( 'seatplan', query.location ).then( data => { + if ( Object.keys( data ).length > 0 ) { + resolve( data[ 'save' ] ); + } else { + reject( 'No data found for this location' ); + } + } ).catch( error => { + reject( error ); + } ); + } + } ); + } +} + +module.exports = GETHandler; \ No newline at end of file diff --git a/src/server/backend/api/postHandler.js b/src/server/backend/api/postHandler.js new file mode 100644 index 0000000..7531b09 --- /dev/null +++ b/src/server/backend/api/postHandler.js @@ -0,0 +1,41 @@ +/* +* libreevent - postHandler.js +* +* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License +* https://janishutz.com, development@janishutz.com +* +* +*/ + +const db = require( '../db/db.js' ); + +class POSTHandler { + constructor () { + + } + + handleCall ( call, data, lang ) { + return new Promise( ( resolve, reject ) => { + console.log( lang ); + if ( call === 'saveSeatplanDraft' ) { + db.getJSONDataSimple( 'seatplan', data.location ).then( res => { + let dat = res; + dat[ 'draft' ] = data.data; + db.writeJSONDataSimple( 'seatplan', data.location, dat ).then( resp => { + resolve( resp ); + } ).catch( error => { + reject( error ); + } ); + } ); + } else if ( call === 'saveSeatplan' ) { + db.writeJSONDataSimple( 'seatplan', data.location, { 'draft': {}, 'save': data.data } ).then( resp => { + resolve( resp ); + } ).catch( error => { + reject( error ); + } ); + } + } ); + } +} + +module.exports = POSTHandler; \ No newline at end of file diff --git a/src/server/backend/db/data/seatplan.json b/src/server/backend/db/data/seatplan.json new file mode 100644 index 0000000..cb710e8 --- /dev/null +++ b/src/server/backend/db/data/seatplan.json @@ -0,0 +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 diff --git a/src/server/backend/db/db.js b/src/server/backend/db/db.js index 2ef5b5d..3baaa52 100644 --- a/src/server/backend/db/db.js +++ b/src/server/backend/db/db.js @@ -26,7 +26,7 @@ if ( settings.db === 'mysql' ) { dbh.connect(); } -module.exports.getDataSimple = function getData ( db, column, searchQuery ) { +module.exports.getDataSimple = ( db, column, searchQuery ) => { return new Promise( resolve => { dbh.query( { 'command': 'getFilteredData', 'property': column, 'searchQuery': searchQuery }, dbRef[ db ] ).then( data => { console.log( data ); @@ -37,23 +37,78 @@ module.exports.getDataSimple = function getData ( db, column, searchQuery ) { } ); }; -module.exports.writeDataSimple = function writeData ( db, column, searchQuery ) { +module.exports.writeDataSimple = ( db, column, searchQuery ) => { return new Promise( ( resolve, reject ) => { } ); }; -module.exports.getJSONData = function getData ( file ) { +module.exports.getJSONData = ( file ) => { return new Promise( ( resolve, reject ) => { - fs.readFile( path.join( __dirname + '/../../' + file ), ( error, data ) => { + fs.readFile( path.join( __dirname + '/data/' + file + '.json' ), ( error, data ) => { if ( error ) { reject( 'Error occurred: Error trace: ' + error ); + } else { + if ( data.byteLength > 0 ) { + resolve( JSON.parse( data ) ?? {} ); + } else { + resolve( { } ); + } } - resolve( JSON.parse( data ) ); } ); } ); }; -module.exports.getJSONDataSync = function getData ( file ) { +module.exports.getJSONDataSimple = ( file, identifier ) => { + return new Promise( ( resolve, reject ) => { + fs.readFile( path.join( __dirname + '/data/' + file + '.json' ), ( error, data ) => { + if ( error ) { + reject( 'Error occurred: Error trace: ' + error ); + } else { + if ( data.byteLength > 0 ) { + resolve( JSON.parse( data )[ identifier ] ?? {} ); + } else { + resolve( { } ); + } + } + } ); + } ); +}; + +module.exports.getJSONDataSync = ( file ) => { return JSON.parse( fs.readFileSync( path.join( __dirname + '/../../' + file ) ) ); +}; + +module.exports.writeJSONDataSimple = ( db, identifier, values ) => { + return new Promise( ( resolve, reject ) => { + fs.readFile( path.join( __dirname + '/data/' + db + '.json' ), ( error, data ) => { + if ( error ) { + reject( 'Error occurred: Error trace: ' + error ); + } else { + let dat = {}; + if ( data.byteLength > 0 ) { + dat = JSON.parse( data ) ?? {}; + } + dat[ identifier ] = values; + fs.writeFile( path.join( __dirname + '/data/' + db + '.json' ), JSON.stringify( dat ), ( error ) => { + if ( error ) { + reject( 'Error occurred: Error trace: ' + error ); + } + resolve( true ); + } ); + } + } ); + } ); +}; + +module.exports.writeJSONData = ( db, data ) => { + return new Promise( ( resolve, reject ) => { + fs.writeFile( path.join( __dirname + '/data/' + db + '.json' ), JSON.stringify( data ), ( error ) => { + if ( error ) { + reject( 'Error occurred: Error trace: ' + error ); + } else { + resolve( true ); + } + } ); + } ); }; \ No newline at end of file diff --git a/src/server/backend/db/db.json b/src/server/backend/db/db.json index e69de29..9e26dfe 100644 --- a/src/server/backend/db/db.json +++ b/src/server/backend/db/db.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/server/backend/userAPIRoutes.js b/src/server/backend/userAPIRoutes.js new file mode 100644 index 0000000..8a95ec0 --- /dev/null +++ b/src/server/backend/userAPIRoutes.js @@ -0,0 +1,35 @@ +/* +* libreevent - adminAPIRoutes.js +* +* Created by Janis Hutz 07/20/2023, Licensed under the GPL V3 License +* https://janishutz.com, development@janishutz.com +* +* +*/ + +const posth = require( './api/postHandler.js' ); +const geth = require( './api/getHandler.js' ); +const postHandler = new posth(); +const getHandler = new geth(); +const path = require( 'path' ); + +// settings is missing in arguments which shouldn't pose any problem +module.exports = ( app ) => { + // Add specific routes here to have them be checked first to not get general handling + + app.get( '/getAPI/:call', ( req, res ) => { + getHandler.handleCall( req.params.call, req.query ).then( data => { + res.send( data ); + } ).catch( error => { + res.status( 500 ).send( error ); + } ); + } ); + + app.post( '/API/:call', ( req, res ) => { + postHandler.handleCall( req.params.call, req.body, req.query.lang ).then( data => { + res.send( data ); + } ).catch( error => { + res.status( 500 ).send( error ); + } ); + } ); +}; \ No newline at end of file diff --git a/src/server/test.js b/src/server/test.js index ed8daa2..2add288 100644 --- a/src/server/test.js +++ b/src/server/test.js @@ -1,9 +1,19 @@ -const express = require( 'express' ); -let app = express(); -const http = require( 'http' ); +const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; +let result = ''; -app.use( express.static( '.' ) ); +// https://stackoverflow.com/questions/36129721/convert-number-to-alphabet-letter +function printToLetter( number ) { + let charIndex = number % alphabet.length; + let quotient = number / alphabet.length; + if ( charIndex - 1 === -1 ) { + charIndex = alphabet.length; + quotient --; + } + result = alphabet.charAt( charIndex - 1 ) + result; + if ( quotient >= 1 ) { + printToLetter( parseInt( quotient ) ); + } +} - -const PORT = process.env.PORT || 8081; -http.createServer( app ).listen( PORT ); \ No newline at end of file +printToLetter( 150036 ); +console.log( result ); \ No newline at end of file diff --git a/src/server/ui/en/errors/403.html b/src/server/ui/en/errors/403.html new file mode 100644 index 0000000..f5bcc1b --- /dev/null +++ b/src/server/ui/en/errors/403.html @@ -0,0 +1,11 @@ + + + + + + 403 + + +

403

+ + \ No newline at end of file diff --git a/src/webapp/main/src/components/seatplan/editor/properties.vue b/src/webapp/main/src/components/seatplan/editor/properties.vue index 226912c..b33d9b2 100644 --- a/src/webapp/main/src/components/seatplan/editor/properties.vue +++ b/src/webapp/main/src/components/seatplan/editor/properties.vue @@ -106,9 +106,7 @@ Category: - + diff --git a/src/webapp/main/src/components/seatplan/editor/window.vue b/src/webapp/main/src/components/seatplan/editor/window.vue index 67affde..053b6bf 100644 --- a/src/webapp/main/src/components/seatplan/editor/window.vue +++ b/src/webapp/main/src/components/seatplan/editor/window.vue @@ -71,7 +71,7 @@ data() { return { active: 0, - 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, 'seatNumberingPosition': 1, 'sector': 'A', 'text': { 'text': 'TestText', 'textSize': 20, 'colour': '#20FFFF' }, 'numberingDirection': 'left' } }, + 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, 'seatNumberingPosition': 1, 'sector': 'A', 'text': { 'text': 'TestText', 'textSize': 20, 'colour': '#20FFFF' }, 'numberingDirection': 'left', 'seatNumberingPosition': 1, 'category': '1' } }, available: { 'redo': false, 'undo': false }, scaleFactor: 1, sizePoll: null, @@ -128,7 +128,41 @@ } }; - this.loadSeatplan(); + // TODO: Create 1min interval saving + + /* + Calculate scale factor (this adds support for differently sized screens) + 900px is the "default" height + */ + + let height = $( document ).height() * 0.8; + this.scaleFactor = ( height / 900 ) * this.zoomFactor; + + /* + Load seatplan + */ + fetch( localStorage.getItem( 'url' ) + '/admin/getAPI/getSeatplanDraft?location=' + sessionStorage.getItem( 'locationID' ) ).then( res => { + if ( res.status === 200 ) { + res.json().then( data => { + this.draggables = this.scaleUp( data.data ); + sessionStorage.setItem( 'seatplan', JSON.stringify( data.data ) ); + for ( let element in this.draggables ) { + if ( this.draggables[ element ].active ) { + this.draggables[ element ].active = false; + } + } + } ); + } else if ( res.status === 500 ) { + if ( sessionStorage.getItem( 'seatplan' ) ) { + this.draggables = this.scaleUp( JSON.parse( sessionStorage.getItem( 'seatplan' ) ) ); + } + for ( let element in this.draggables ) { + if ( this.draggables[ element ].active ) { + this.draggables[ element ].active = false; + } + } + } + } ); if ( !sessionStorage.getItem( 'seatplan-history' ) ) { sessionStorage.setItem( 'seatplan-history', JSON.stringify( { '1': this.scaleDown( this.draggables ) } ) ); @@ -163,10 +197,7 @@ let height = $( document ).height() * 0.8; this.scaleFactor = ( height / 900 ) * this.zoomFactor; - /* - Load seatplan - */ - // TODO: load from server + if ( sessionStorage.getItem( 'seatplan' ) ) { this.draggables = this.scaleUp( JSON.parse( sessionStorage.getItem( 'seatplan' ) ) ); } @@ -263,21 +294,59 @@ sessionStorage.setItem( 'seatplan', JSON.stringify( this.scaleDown( this.draggables ) ) ); }, saveDraft () { - // TODO: Save seat count and seat config to server as well + this.getSeatCount(); let progressNotification = this.$refs.notification.createNotification( 'Saving as draft', 5, 'progress', 'normal' ); sessionStorage.setItem( 'seatplan', JSON.stringify( this.scaleDown( this.draggables ) ) ); - this.$refs.notification.createNotification( 'Saved as draft', 5, 'ok', 'normal' ); - // TODO: Save to server and add warning if no component has a seat start point if any component is a seat component + const options = { + method: 'post', + body: JSON.stringify( { 'data':{ 'seatInfo': this.seatCountInfo, 'data': this.scaleDown( this.draggables ) }, 'location': sessionStorage.getItem( 'locationID' ) } ), + headers: { + 'Content-Type': 'application/json', + 'charset': 'utf-8' + } + }; + fetch( localStorage.getItem( 'url' ) + '/admin/api/saveSeatplanDraft', options ).then( res => { + if ( res.status === 200 ) { + res.text().then( text => { + console.log( text ); + this.$refs.notification.cancelNotification( progressNotification ); + this.$refs.notification.createNotification( 'Saved as draft', 5, 'ok', 'normal' ); + } ); + } else if ( res.status === 403 ) { + this.$refs.notification.cancelNotification( progressNotification ); + this.$refs.notification.createNotification( 'Unauthenticated', 5, 'ok', 'error' ); + } + } ); + // TODO: add warning if no component has a seat start point if any component is a seat component }, deploy () { - // TODO: Save to server - this.$refs.notification.createNotification( 'Deploying...', 5, 'progress', 'normal' ); - this.$refs.notification.createNotification( 'Deployed successfully', 5, 'ok', 'normal' ); + let deployNotification = this.$refs.notification.createNotification( 'Deploying...', 5, 'progress', 'normal' ); + const options = { + method: 'post', + body: JSON.stringify( { 'data':{ 'seatInfo': this.seatCountInfo, 'data': this.scaleDown( this.draggables ) }, 'location': sessionStorage.getItem( 'locationID' ) } ), + headers: { + 'Content-Type': 'application/json', + 'charset': 'utf-8' + } + }; + fetch( localStorage.getItem( 'url' ) + '/admin/api/saveSeatplan', options ).then( res => { + if ( res.status === 200 ) { + res.text().then( text => { + console.log( text ); + this.$refs.notification.cancelNotification( deployNotification ); + this.$refs.notification.createNotification( 'Deployed successfully', 5, 'ok', 'normal' ); + } ); + } else if ( res.status === 403 ) { + this.$refs.notification.cancelNotification( deployNotification ); + this.$refs.notification.createNotification( 'Unauthenticated', 5, 'ok', 'error' ); + } + } ); + // TODO: add warning if no component has a seat start point if any component is a seat component }, addNewElement () { // TODO: Check that this algorithm actually works in practice. If not, replace with one that // searches for the first available ID or uses a var to determine ID. - this.draggables[ Object.keys( this.draggables ).length + 1 ] = { 'x': 100, 'y':100, 'h': 100, 'w': 250, 'active': false, 'draggable': true, 'resizable': true, 'id': Object.keys( this.draggables ).length + 1, 'origin': 1, 'shape':'rectangular', 'type': 'seat', 'startingRow': 1, 'seatNumberingPosition': Object.keys( this.draggables ).length, 'sector': 'A', 'text': { 'text': 'TestText', 'textSize': 20, 'colour': '#20FFFF' }, 'ticketCount': 1, 'numberingDirection': 'left' }; + this.draggables[ Object.keys( this.draggables ).length + 1 ] = { 'x': 100, 'y':100, 'h': 100, 'w': 250, 'active': false, 'draggable': true, 'resizable': true, 'id': ( Object.keys( this.draggables ).length + 1 ), 'origin': 1, 'shape':'rectangular', 'type': 'seat', 'startingRow': 1, 'seatNumberingPosition': Object.keys( this.draggables ).length, 'sector': 'A', 'text': { 'text': 'TestText', 'textSize': 20, 'colour': '#20FFFF' }, 'ticketCount': 1, 'numberingDirection': 'left', 'category': '1' }; this.saveHistory(); document.getElementById( 'parent' ).scrollTop = 0; document.getElementById( 'parent' ).scrollLeft = 0; diff --git a/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/seats/circular.vue b/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/seats/circular.vue index 21a3e56..bc783f8 100644 --- a/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/seats/circular.vue +++ b/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/seats/circular.vue @@ -14,9 +14,9 @@ living close + :title="seat.displayName + ', Unavailable'">disabled_by_default done + :title="seat.displayName + ', Selected'" @click="deselectSeat( seat.row, seat.seat )">check_box @@ -54,7 +54,11 @@ export default { }, data: { type: Object, - "default": { 'sector': 'A', 'sectorCount': 1, 'unavailableSeats': { 'secAr0s0': true } } + "default": { 'sector': 'A', 'sectorCount': 1, 'unavailableSeats': { 'secAr0s0': 'nav' }, 'categoryInfo': { 'pricing': { '1': { 'displayName': 'Adults - CHF 20.-', 'value': '1', 'price': 20 }, '2': { 'displayName': 'Child (0 - 15.99y) - CHF 15.-', 'value': '2', 'price': 15 } } } } + }, + id: { + type: Number, + "default": 1, } }, data () { @@ -76,7 +80,7 @@ export default { let r = row * size; this.seats[ row ] = {}; for ( let n = 0; n < nn; n++ ) { - this.seats[ row ][ n ] = { 'style': '', 'id': 'sec' + this.data.sector + 'r' + row + 's' + n, 'displayName': ( this.data.sectorCount > 1 ? 'Sector ' + this.data.sector + ', ' : '' ) + 'Row ' + ( row + 1 ) + ', Seat ' + ( n + 1 ), 'status': 'av', 'row': row, 'seat': n }; + this.seats[ row ][ n ] = { 'style': '', 'id': 'sec' + this.data.sector + 'r' + row + 's' + n, 'displayName': ( this.data.sectorCount > 1 ? 'Sector ' + this.data.sector + ', ' : '' ) + 'Row ' + row + ', Seat ' + ( n + 1 ), 'status': 'av', 'row': row, 'seat': n }; let phi = n * size / ( row * size ); if ( this.origin === 1 ) { this.seats[ row ][ n ][ 'style' ] = `bottom: ${ r * Math.cos( phi ) * this.scaleFactor }px; left: ${ r * Math.sin( phi ) * this.scaleFactor }px; rotate: ${ phi }rad`; @@ -88,19 +92,42 @@ export default { this.seats[ row ][ n ][ 'style' ] = `top: ${ r * Math.cos( phi ) * this.scaleFactor }px; left: ${ r * Math.sin( phi ) * this.scaleFactor }px; rotate: ${ Math.PI - phi }rad`; } this.seats[ row ][ n ][ 'scaling' ] = `font-size: ${this.scaleFactor * 200}%; `; + if ( this.data.categoryInfo.color ) { + this.seats[ row ][ n ][ 'style' ] += `color: ${ this.data.categoryInfo.color.fg ? this.data.categoryInfo.color.fg : 'black' }; background-color: ${ this.data.categoryInfo.color.bg ? this.data.categoryInfo.color.bg : 'rgba( 0, 0, 0, 0 )' }`; + } + + if ( this.data.unavailableSeats ) { + if ( this.data.unavailableSeats[ this.seats[ row ][ n ][ 'id' ] ] ) { + this.seats[ row ][ n ][ 'status' ] = this.data.unavailableSeats[ this.seats[ row ][ n ][ 'id' ] ]; + } + } } } }, setScaleFactor () { for ( let row in this.seats ) { for ( let seat in this.seats[ row ] ) { - let styles = this.seats[ row ][ seat ].style.substring( this.seats[ row ][ seat ].style.indexOf( ';' ) + 1 ); - this.seats[ row ][ seat ].style = `font-size: ${this.scaleFactor * 200}%;` + styles; + this.seats[ row ][ seat ].scaling = `font-size: ${this.scaleFactor * 200}%;`; } } }, selectSeat ( row, seat ) { - console.log( row + ' ' + seat ); + console.log( this.data.categoryInfo ); + let selectedSeat = this.seats[ row ][ seat ]; + selectedSeat[ 'sector' ] = this.data.sector; + selectedSeat[ 'option' ] = this.data.categoryInfo.pricing; + selectedSeat[ 'componentID' ] = this.id; + console.log( selectedSeat ); + this.$emit( 'seatSelected', selectedSeat ); + }, + deselectSeat( row, seat ) { + this.$emit( 'seatDeselected', this.seats[ row ][ seat ] ); + this.seats[ row ][ seat ][ 'status' ] = 'av'; + }, + validateSeatSelection( seatObject, selectedTicketOption ) { + console.log( seatObject ); + this.seats[ seatObject[ 'row' ] ][ seatObject[ 'seat' ] ][ 'status' ] = 'sel'; + this.seats[ seatObject[ 'row' ] ][ seatObject[ 'seat' ] ][ 'ticketOption' ] = selectedTicketOption; } }, watch: { diff --git a/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/seats/rectangular.vue b/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/seats/rectangular.vue index c7706d5..69ccb27 100644 --- a/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/seats/rectangular.vue +++ b/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/seats/rectangular.vue @@ -44,6 +44,10 @@ export default { type: Number, "default": 1, }, + startingRow: { + type: Number, + "default": 1, + }, origin: { type: Number, "default": 1, @@ -74,7 +78,6 @@ export default { this.seats[ row ] = {}; for ( let n = 0; n < Math.floor( w / size ); n++ ) { this.seats[ row ][ n ] = { 'style': '', 'id': 'sec' + this.data.sector + 'r' + row + 's' + n, 'displayName': ( this.data.sectorCount > 1 ? 'Sector ' + this.data.sector + ', ' : '' ) + 'Row ' + ( row + 1 ) + ', Seat ' + ( n + 1 ), 'status': 'av', 'row': row, 'seat': n }; - // TODO: apply style of category if ( this.origin === 1 ) { this.seats[ row ][ n ][ 'style' ] = `bottom: ${ row * size * this.scaleFactor }px; left: ${ n * size * this.scaleFactor }px; rotate: ${ this.origin / 4 - 0.25 }turn;`; } else if ( this.origin === 2 ) { @@ -84,7 +87,9 @@ export default { } else if ( this.origin === 4 ) { this.seats[ row ][ n ][ 'style' ] = `top: ${ row * size * this.scaleFactor }px; left: ${ n * size * this.scaleFactor }px; rotate: ${ this.origin / 4 - 0.25 }turn;`; } + this.seats[ row ][ n ][ 'scaling' ] = `font-size: ${this.scaleFactor * 200}%; `; + if ( this.data.categoryInfo.color ) { this.seats[ row ][ n ][ 'style' ] += `color: ${ this.data.categoryInfo.color.fg ? this.data.categoryInfo.color.fg : 'black' }; background-color: ${ this.data.categoryInfo.color.bg ? this.data.categoryInfo.color.bg : 'rgba( 0, 0, 0, 0 )' }`; } @@ -100,8 +105,7 @@ export default { setScaleFactor () { for ( let row in this.seats ) { for ( let seat in this.seats[ row ] ) { - let styles = this.seats[ row ][ seat ].style.substring( this.seats[ row ][ seat ].style.indexOf( ';' ) + 1 ); - this.seats[ row ][ seat ].style = `font-size: ${this.scaleFactor * 200}%;` + styles; + this.seats[ row ][ seat ].scaling = `font-size: ${this.scaleFactor * 200}%;`; } } }, diff --git a/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/seats/trapezoid.vue b/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/seats/trapezoid.vue index caddb35..8f2bda6 100644 --- a/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/seats/trapezoid.vue +++ b/src/webapp/main/src/components/seatplan/userApp/seatplanComponents/seats/trapezoid.vue @@ -14,9 +14,9 @@ living close + :title="seat.displayName + ', Unavailable'">disabled_by_default done + :title="seat.displayName + ', Selected'" @click="deselectSeat( seat.row, seat.seat )">check_box @@ -54,7 +54,11 @@ export default { }, data: { type: Object, - "default": { 'sector': 'A', 'sectorCount': 1, 'unavailableSeats': { 'secAr0s0': true } } + "default": { 'sector': 'A', 'sectorCount': 1, 'unavailableSeats': { 'secAr0s0': 'nav' }, 'categoryInfo': { 'pricing': { '1': { 'displayName': 'Adults - CHF 20.-', 'value': '1', 'price': 20 }, '2': { 'displayName': 'Child (0 - 15.99y) - CHF 15.-', 'value': '2', 'price': 15 } } } } + }, + id: { + type: Number, + "default": 1, } }, data () { @@ -79,7 +83,7 @@ export default { let nn = 2 + ( row - 1 ) * 2; this.seats[ row ] = {}; for ( let n = 0; n < nn; n++ ) { - this.seats[ row ][ n ] = { 'style': '', 'id': 'sec' + this.data.sector + 'r' + row + 's' + n, 'displayName': ( this.data.sectorCount > 1 ? 'Sector ' + this.data.sector + ', ' : '' ) + 'Row ' + ( row + 1 ) + ', Seat ' + ( n + 1 ), 'status': 'av', 'row': row, 'seat': n }; + this.seats[ row ][ n ] = { 'style': '', 'id': 'sec' + this.data.sector + 'r' + row + 's' + n, 'displayName': ( this.data.sectorCount > 1 ? 'Sector ' + this.data.sector + ', ' : '' ) + 'Row ' + row + ', Seat ' + ( n + 1 ), 'status': 'av', 'row': row, 'seat': n }; let side = n * sideOffset; if ( this.origin === 1 ) { this.seats[ row ][ n ][ 'style' ] = `bottom: ${ ( side + 5 ) * this.scaleFactor }px; left: ${ ( row * sideOffset * 2 - side ) * this.scaleFactor }px; rotate: ${ angle }rad`; @@ -90,20 +94,43 @@ export default { } else if ( this.origin === 4 ) { this.seats[ row ][ n ][ 'style' ] = `top: ${ ( side + 5 ) * this.scaleFactor }px; left: ${ ( row * sideOffset * 2 - side ) * this.scaleFactor }px; rotate: ${ Math.PI - angle }rad`; } + this.seats[ row ][ n ][ 'scaling' ] = `font-size: ${this.scaleFactor * 200}%; `; + + if ( this.data.categoryInfo.color ) { + this.seats[ row ][ n ][ 'style' ] += `color: ${ this.data.categoryInfo.color.fg ? this.data.categoryInfo.color.fg : 'black' }; background-color: ${ this.data.categoryInfo.color.bg ? this.data.categoryInfo.color.bg : 'rgba( 0, 0, 0, 0 )' }`; + } + + if ( this.data.unavailableSeats ) { + if ( this.data.unavailableSeats[ this.seats[ row ][ n ][ 'id' ] ] ) { + this.seats[ row ][ n ][ 'status' ] = this.data.unavailableSeats[ this.seats[ row ][ n ][ 'id' ] ]; + } + } } } }, setScaleFactor () { for ( let row in this.seats ) { for ( let seat in this.seats[ row ] ) { - let styles = this.seats[ row ][ seat ].style.substring( this.seats[ row ][ seat ].style.indexOf( ';' ) + 1 ); - this.seats[ row ][ seat ].style = `font-size: ${this.scaleFactor * 200}%;` + styles; + this.seats[ row ][ seat ].scaling = `font-size: ${this.scaleFactor * 200}%;`; } } }, selectSeat ( row, seat ) { - console.log( row + ' ' + seat ); + let selectedSeat = this.seats[ row ][ seat ]; + selectedSeat[ 'sector' ] = this.data.sector; + selectedSeat[ 'option' ] = this.data.categoryInfo.pricing; + selectedSeat[ 'componentID' ] = this.id; + this.$emit( 'seatSelected', selectedSeat ); + }, + deselectSeat( row, seat ) { + this.$emit( 'seatDeselected', this.seats[ row ][ seat ] ); + this.seats[ row ][ seat ][ 'status' ] = 'av'; + }, + validateSeatSelection( seatObject, selectedTicketOption ) { + console.log( seatObject ); + this.seats[ seatObject[ 'row' ] ][ seatObject[ 'seat' ] ][ 'status' ] = 'sel'; + this.seats[ seatObject[ 'row' ] ][ seatObject[ 'seat' ] ][ 'ticketOption' ] = selectedTicketOption; } }, watch: { diff --git a/src/webapp/main/src/components/seatplan/userApp/userWindow.vue b/src/webapp/main/src/components/seatplan/userApp/userWindow.vue index 9e46d81..24b9fd8 100644 --- a/src/webapp/main/src/components/seatplan/userApp/userWindow.vue +++ b/src/webapp/main/src/components/seatplan/userApp/userWindow.vue @@ -15,16 +15,19 @@ - + - + - @@ -110,12 +113,25 @@ } }; - // Load cart this.cart = localStorage.getItem( 'cart' ) ? JSON.parse( localStorage.getItem( 'cart' ) ): {}; - // Load seat plan - this.loadSeatplan(); + + // Load seatplan from server + let height = $( document ).height() * 0.8; + this.scaleFactor = ( height / 900 ) * this.zoomFactor; + fetch( localStorage.getItem( 'url' ) + '/getAPI/getSeatplan?location=' + sessionStorage.getItem( 'selectedTicket' ) ).then( res => { + if ( res.status === 200 ) { + res.json().then( data => { + this.draggables = this.scaleUp( data.data ); + sessionStorage.setItem( 'seatplan', JSON.stringify( data.data ) ); + } ); + } else if ( res.status === 500 ) { + if ( sessionStorage.getItem( 'seatplan' ) ) { + this.draggables = this.scaleUp( JSON.parse( sessionStorage.getItem( 'seatplan' ) ) ); + } + } + } ); // TODO: remove scaleDown function again once backend is up // TODO: Optimise for odd screen sizes and aspect ratios and fucking webkit @@ -188,7 +204,6 @@ /* Load seatplan */ - // TODO: load from server if ( sessionStorage.getItem( 'seatplan' ) ) { this.draggables = this.scaleUp( JSON.parse( sessionStorage.getItem( 'seatplan' ) ) ); } @@ -218,7 +233,7 @@ 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' ]; @@ -286,6 +301,7 @@ 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 ); diff --git a/src/webapp/main/src/main.js b/src/webapp/main/src/main.js index 6303a82..f83319b 100644 --- a/src/webapp/main/src/main.js +++ b/src/webapp/main/src/main.js @@ -19,7 +19,7 @@ app.use( createPinia() ); let userStore = useUserStore(); -let prod = false; +let prod = true; if ( prod ) { fetch( '/api/getAuth' ).then( res => { diff --git a/src/webapp/main/src/views/purchasing/PurchaseView.vue b/src/webapp/main/src/views/purchasing/PurchaseView.vue index d3f94a9..e325976 100644 --- a/src/webapp/main/src/views/purchasing/PurchaseView.vue +++ b/src/webapp/main/src/views/purchasing/PurchaseView.vue @@ -181,11 +181,12 @@ export default { name: 'PurchaseView', data () { return { - settings: { 'accountRequired': true, 'requiresAddress': true, 'requiresAge': true, 'requiresSpecialNumber': true, 'specialNumberDisplayName': { 'de': '', 'en': 'id number' } }, + settings: { 'accountRequired': true, 'requiresAddress': true, 'requiresAge': true, 'requiresSpecialNumber': true, 'specialRequirement': { 'display': { 'de': '', 'en': 'id number' }, 'rules': {} } }, isAuthenticated: false, cart: {}, backend: { 'currency': 'CHF' }, cartNotEmpty: false, + userData: {}, } }, computed: {