mirror of
https://github.com/janishutz/libreevent.git
synced 2025-11-25 13:24:24 +00:00
working location settings (incl saving)
This commit is contained in:
@@ -22,7 +22,7 @@ module.exports = ( app ) => {
|
||||
getHandler.handleCall( req.params.call, req.query ).then( data => {
|
||||
res.send( data );
|
||||
} ).catch( error => {
|
||||
res.status( 500 ).send( error );
|
||||
res.status( error.code ).send( error.error );
|
||||
} );
|
||||
} else {
|
||||
res.status( 403 ).sendFile( path.join( __dirname + '/../ui/' + ( req.query.lang ?? 'en' ) + '/errors/403.html' ) );
|
||||
@@ -34,7 +34,7 @@ module.exports = ( app ) => {
|
||||
postHandler.handleCall( req.params.call, req.body, req.query.lang ).then( data => {
|
||||
res.send( data );
|
||||
} ).catch( error => {
|
||||
res.status( 500 ).send( error );
|
||||
res.status( error.code ).send( error.error );
|
||||
} );
|
||||
} else {
|
||||
res.status( 403 ).sendFile( path.join( __dirname + '/../ui/' + ( req.query.lang ?? 'en' ) + '/errors/403.html' ) );
|
||||
|
||||
@@ -21,10 +21,10 @@ class GETHandler {
|
||||
if ( Object.keys( data ).length > 0 ) {
|
||||
resolve( data[ 'save' ] );
|
||||
} else {
|
||||
reject( 'No data found for this location' );
|
||||
reject( { 'code': 400, 'error': 'No data found for this location' } );
|
||||
}
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'getSeatplanDraft' ) {
|
||||
db.getJSONDataSimple( 'seatplan', query.location ).then( data => {
|
||||
@@ -35,11 +35,19 @@ class GETHandler {
|
||||
resolve( data[ 'save' ] );
|
||||
}
|
||||
} else {
|
||||
reject( 'No data found for this location' );
|
||||
reject( { 'code': 400, 'error': 'No data found for this location' } );
|
||||
}
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
} );
|
||||
} else if ( call === 'getLocations' ) {
|
||||
db.getJSONData( 'locations' ).then( data => {
|
||||
resolve( data );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else {
|
||||
reject( { 'code': 404, 'error': 'Route not found' } );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -24,15 +24,38 @@ class POSTHandler {
|
||||
db.writeJSONDataSimple( 'seatplan', data.location, dat ).then( resp => {
|
||||
resolve( resp );
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} );
|
||||
} else if ( call === 'saveSeatplan' ) {
|
||||
db.writeJSONDataSimple( 'seatplan', data.location, { 'draft': {}, 'save': data.data } ).then( resp => {
|
||||
resolve( resp );
|
||||
} ).catch( error => {
|
||||
reject( error );
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'saveLocations' ) {
|
||||
db.getJSONData( 'seatplan' ).then( res => {
|
||||
let dat = res;
|
||||
for ( let loc in data.updated ) {
|
||||
if ( res[ loc ] ) {
|
||||
dat[ data.updated[ loc ] ] = res[ loc ];
|
||||
delete dat[ loc ];
|
||||
}
|
||||
}
|
||||
db.writeJSONData( 'seatplan', dat ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
|
||||
db.writeJSONData( 'locations', data.data ).then( resp => {
|
||||
resolve( resp );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else {
|
||||
reject( { 'code': 404, 'error': 'Route not found' } );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
1
src/server/backend/db/data/locations.json
Normal file
1
src/server/backend/db/data/locations.json
Normal file
@@ -0,0 +1 @@
|
||||
{"test2":{"locationID":"test2","name":"TestLocation2","seatplan-enabled":true},"test":{"locationID":"test","name":"TestLocation","seatplan-enabled":true}}
|
||||
@@ -15,7 +15,7 @@
|
||||
<h3>{{ data.message }}</h3>
|
||||
<settings v-model:settings="data.options"></settings>
|
||||
<div class="button-wrapper">
|
||||
<button @click="closePopup( 'ok' )" title="Save changes">Save</button>
|
||||
<button @click="submitSettings( 'ok' )" title="Save changes">Save</button>
|
||||
<button @click="closePopup( 'cancel' )" title="Cancel changes">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -121,6 +121,17 @@
|
||||
this.data[ 'selected' ] = data;
|
||||
this.closePopup( message );
|
||||
},
|
||||
submitSettings () {
|
||||
$( '#popup-backdrop' ).fadeOut( 300 );
|
||||
const dat = this.data.options;
|
||||
let ret = {};
|
||||
for ( let setting in dat ) {
|
||||
if ( dat[ setting ][ 'type' ] !== 'link' ) {
|
||||
ret[ setting ] = dat[ setting ][ 'value' ];
|
||||
}
|
||||
}
|
||||
this.$emit( 'data', { 'data': ret, 'status': 'settings' } );
|
||||
},
|
||||
openPopup ( message, options, dataType, selected ) {
|
||||
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';
|
||||
|
||||
@@ -79,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' } }, 'maxTickets': 2 },
|
||||
event: { 'name': 'TestEvent2', 'location': 'TestLocation2', 'date': '2023-07-15', '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,
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<div class="location-app" v-if="Object.keys( locations ).length">
|
||||
<ul>
|
||||
<li v-for="location in locations">
|
||||
<div class="location" @click="selectLocation( location.locationID );" title="Edit this location">
|
||||
<div class="location" @click="selectLocation( location.locationID );" title="Edit this location" @contextmenu="( e ) => { e.preventDefault(); openRightClickMenu( location.locationID, e ); }">
|
||||
<div class="location-name">
|
||||
<h3>{{ location.name }}</h3>
|
||||
<h3>{{ location.locationID }} ({{ location.name }})</h3>
|
||||
<p v-if="location['seatplan-enabled']">This location has a seatplan.</p>
|
||||
<p v-else>This location has NO seatplan.</p>
|
||||
</div>
|
||||
@@ -28,39 +28,54 @@
|
||||
<div v-else class="no-location-hint">
|
||||
No locations configured, please <b @click="addLocation();" style="cursor: pointer;">add</b> one
|
||||
</div>
|
||||
<popups ref="popup" size="big"></popups>
|
||||
<popups ref="popup2" size="huge"></popups>
|
||||
<popups ref="popup" size="big" @data="( data ) => {
|
||||
handleData( data );
|
||||
}"></popups>
|
||||
<rightClickMenu ref="rclk" @command="( command ) => { executeCommand( command ) }"></rightClickMenu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import popups from '@/components/notifications/popups.vue';
|
||||
import rightClickMenu from '@/components/settings/rightClickMenu.vue';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
locations: { 'test':{ 'name':'TestLocation', 'locationID':'test', 'seatplan-enabled': true, 'seatplan': {} } },
|
||||
currentlyOpenMenu: '',
|
||||
currentPopup: '',
|
||||
updatedLocations: {}
|
||||
}
|
||||
},
|
||||
components: {
|
||||
popups,
|
||||
rightClickMenu,
|
||||
},
|
||||
methods: {
|
||||
selectLocation ( locationID ) {
|
||||
sessionStorage.setItem( 'locationID', locationID );
|
||||
this.currentlyOpenMenu = locationID;
|
||||
this.$refs.popup.openPopup( 'Settings for ' + this.locations[ locationID ][ 'name' ], {
|
||||
'locationName': {
|
||||
'display': 'Location name',
|
||||
'id': 'locationName',
|
||||
'tooltip':'Give the location the event takes place a name. This name will also be shown to customers',
|
||||
'value': '',
|
||||
'locationID': {
|
||||
'display': 'Internal location name',
|
||||
'id': 'locationID',
|
||||
'tooltip':'Give the location where the event takes place a name. This name will not be shown to the customers and is used for the backend and admin portal. Has to be unique',
|
||||
'value': locationID,
|
||||
'type': 'text',
|
||||
},
|
||||
'usesSeatplan': {
|
||||
'name': {
|
||||
'display': 'Public location name',
|
||||
'id': 'name',
|
||||
'tooltip':'The name of the location that is shown to the customers.',
|
||||
'value': this.locations[ locationID ][ 'name' ],
|
||||
'type': 'text',
|
||||
},
|
||||
'seatplan-enabled': {
|
||||
'display': 'Use seat plan?',
|
||||
'id': 'usesSeatplan',
|
||||
'id': 'seatplan-enabled',
|
||||
'tooltip':'With this toggle you may specify whether or not this location has a seat plan or not.',
|
||||
'value': true,
|
||||
'value': this.locations[ locationID ][ 'seatplan-enabled' ],
|
||||
'type': 'toggle',
|
||||
},
|
||||
'seatplanEditor': {
|
||||
@@ -78,16 +93,23 @@
|
||||
},
|
||||
addLocation () {
|
||||
this.$refs.popup.openPopup( 'Add a new location', {
|
||||
'locationName': {
|
||||
'display': 'Location name',
|
||||
'id': 'locationName',
|
||||
'tooltip':'Give the location the event takes place a name. This name will also be shown to customers',
|
||||
'locationID': {
|
||||
'display': 'Internal location name',
|
||||
'id': 'locationID',
|
||||
'tooltip':'Give the location where the event takes place a name. This name will not be shown to the customers and is used for the backend and admin portal. Has to be unique',
|
||||
'value': '',
|
||||
'type': 'text',
|
||||
},
|
||||
'usesSeatplan': {
|
||||
'name': {
|
||||
'display': 'Public location name',
|
||||
'id': 'name',
|
||||
'tooltip':'The name of the location that is shown to the customers.',
|
||||
'value': '',
|
||||
'type': 'text',
|
||||
},
|
||||
'seatplan-enabled': {
|
||||
'display': 'Use seat plan?',
|
||||
'id': 'usesSeatplan',
|
||||
'id': 'seatplan-enabled',
|
||||
'tooltip':'With this toggle you may specify whether or not this location has a seat plan or not.',
|
||||
'value': true,
|
||||
'type': 'toggle',
|
||||
@@ -105,6 +127,62 @@
|
||||
}
|
||||
, 'settings' );
|
||||
},
|
||||
openRightClickMenu( id, event ) {
|
||||
this.$refs.rclk.openRightClickMenu( event, { 'edit': { 'command': 'editLocation', 'symbol': 'edit', 'display': 'Edit location' }, 'editor': { 'command': 'openEditor', 'symbol': 'tune', 'display': 'Edit seatplan' }, 'delete': { 'command': 'deleteLocation', 'symbol': 'delete', 'display': 'Delete location' } } )
|
||||
this.currentlyOpenMenu = id;
|
||||
},
|
||||
executeCommand( command ) {
|
||||
if ( command === 'editLocation' ) {
|
||||
this.selectLocation( this.currentlyOpenMenu );
|
||||
} else if ( command === 'deleteLocation' ) {
|
||||
this.$refs.popup.openPopup( 'Do you really want to delete the location ' + this.currentlyOpenMenu + '?', {}, 'confirm' );
|
||||
this.currentPopup = 'delete';
|
||||
} else if ( command === 'openEditor' ) {
|
||||
sessionStorage.setItem( 'locationID', this.currentlyOpenMenu );
|
||||
this.$router.push( '/admin/seatplan' );
|
||||
}
|
||||
},
|
||||
handleData ( data ) {
|
||||
if ( this.currentPopup === 'delete' ) {
|
||||
this.currentPopup = '';
|
||||
if ( data.status === 'ok' ) {
|
||||
delete this.locations[ this.currentlyOpenMenu ];
|
||||
}
|
||||
} else {
|
||||
if ( data.status === 'settings' ) {
|
||||
if ( data.data.locationID !== this.currentlyOpenMenu && this.currentlyOpenMenu !== '' ) {
|
||||
delete this.locations[ this.currentlyOpenMenu ];
|
||||
this.updatedLocations[ this.currentlyOpenMenu ] = data.data.locationID;
|
||||
}
|
||||
this.locations[ data.data.locationID ] = data.data;
|
||||
this.currentlyOpenMenu = '';
|
||||
const options = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( { 'updated': this.updatedLocations, 'data': this.locations } ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
}
|
||||
};
|
||||
fetch( localStorage.getItem( 'url' ) + '/admin/api/saveLocations', options ).then( res => {
|
||||
if ( res.status === 200 ) {
|
||||
res.text().then( text => {
|
||||
console.log( text );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
created () {
|
||||
fetch( localStorage.getItem( 'url' ) + '/admin/getAPI/getLocations' ).then( res => {
|
||||
res.json().then( data => {
|
||||
this.locations = data;
|
||||
} ).catch( error => {
|
||||
console.error( error );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -149,4 +227,8 @@
|
||||
margin-right: auto;
|
||||
max-width: 35%;
|
||||
}
|
||||
|
||||
.no-location-hint {
|
||||
margin-top: 5%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
<table class="category">
|
||||
<tr>
|
||||
<td>Event location</td>
|
||||
<td>
|
||||
<td v-if="Object.keys( locations ).length > 0">
|
||||
<select v-model="event.location" class="small-text">
|
||||
<option value="TestLocation">TestLocation</option>
|
||||
<option v-for="location in locations" value="location.locationID">{{ location.locationID }} ({{ location.name }})</option>
|
||||
</select>
|
||||
</td>
|
||||
<td v-else>No locations configured yet. Configure one <router-link to="/admin/locations">here</router-link></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Event date</td>
|
||||
@@ -38,12 +39,12 @@
|
||||
<div class="category-wrapper">
|
||||
<table class="category" v-for="category in event.categories">
|
||||
{{ category.name }}
|
||||
<tr v-for="price in category.price">
|
||||
<tr v-for="ageGroup in event.ageGroups">
|
||||
<td>
|
||||
<div class="category-details">{{ price.name }}:</div>
|
||||
<div class="category-details">{{ ageGroup.name }}<div style="display: inline;" v-if="ageGroup.age"> ({{ ageGroup.age }})</div>:</div>
|
||||
</td>
|
||||
<td>
|
||||
<input type="number" v-model="price.price">
|
||||
<input type="number" v-model="category.price[ ageGroup.id ]">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -118,10 +119,18 @@
|
||||
this.$router.push( '/admin/events' );
|
||||
}
|
||||
this.eventID = sessionStorage.getItem( 'selectedTicket' );
|
||||
fetch( localStorage.getItem( 'url' ) + '/admin/getAPI/getLocations' ).then( res => {
|
||||
res.json().then( data => {
|
||||
this.locations = data;
|
||||
} ).catch( error => {
|
||||
console.error( error );
|
||||
} );
|
||||
} );
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
event: { 'name': 'TestEvent', 'description': 'This is a description for the TestEvent to test multiline support and proper positioning of the Fields', 'freeSeats': 2, 'maxSeats': 2, 'date':'TestDate', 'startingPrice':15, 'location': 'TestLocation', 'eventID': 'test', 'currency': 'CHF', 'logo': 'logo.png', 'categories': { '1': { 'price': { '1': { 'price':25, 'name':'Child (0-15.99 years)'}, '2': { 'price':35, 'name':'Adult'} }, 'bg': 'black', 'fg': 'white', 'name': 'Category 1' }, '2': { 'price': { '1': { 'price':25, 'name':'Child (0-15.99 years)' }, '2': { 'price':35, 'name':'Adult'} }, 'bg': 'green', 'fg': 'white', 'name': 'Category 2' } } },
|
||||
locations: {},
|
||||
event: { 'name': 'TestEvent', 'description': 'This is a description for the TestEvent to test multiline support and proper positioning of the Fields', 'location': 'test', 'date': '2023-07-15', '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 },
|
||||
specialSettings: {
|
||||
'currency': {
|
||||
'display': 'Currency',
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
<!--
|
||||
* libreevent - TicketsDetailsView.vue
|
||||
*
|
||||
* Created by Janis Hutz 05/14/2023, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="details">
|
||||
<h2>{{ event.name }}</h2>
|
||||
<div class="category-wrapper">
|
||||
<table class="category">
|
||||
<tr>
|
||||
<td>Location name</td>
|
||||
<td>
|
||||
<input type="text" v-model="event.name">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Seat plan editor</td>
|
||||
<router-link to="/admin/seatplan">Edit seat plan</router-link>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<button @click="openPopup()">Open Popup</button>
|
||||
<popups ref="popup" @data="( event ) => { popupReturnHandling( event ) }"></popups>
|
||||
<notifications ref="notification" location="topright"></notifications>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.details {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.ticket-settings {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.category-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.category {
|
||||
width: 50%;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.category-details {
|
||||
margin-left: 7%;;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import settings from '@/components/settings/settings.vue';
|
||||
import notifications from '@/components/notifications/notifications.vue';
|
||||
import popups from '@/components/notifications/popups.vue';
|
||||
|
||||
export default {
|
||||
name: 'TicketsDetailsView',
|
||||
components: {
|
||||
settings,
|
||||
notifications,
|
||||
popups,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
event: { 'name': 'TestEvent', 'description': 'This is a description for the TestEvent to test multiline support and proper positioning of the Fields', 'freeSeats': 2, 'maxSeats': 2, 'date':'TestDate', 'startingPrice':15, 'location': 'TestLocation', 'eventID': 'test', 'currency': 'CHF', 'logo': 'logo.png', 'categories': { '1': { 'price': { '1': { 'price':25, 'name':'Child (0-15.99 years)'}, '2': { 'price':35, 'name':'Adult'} }, 'bg': 'black', 'fg': 'white', 'name': 'Category 1' }, '2': { 'price': { '1': { 'price':25, 'name':'Child (0-15.99 years)' }, '2': { 'price':35, 'name':'Adult'} }, 'bg': 'green', 'fg': 'white', 'name': 'Category 2' } } },
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openPopup() {
|
||||
console.log( 'opening' );
|
||||
this.$refs.popup.openPopup();
|
||||
},
|
||||
popupReturnHandling( event ) {
|
||||
console.log( event );
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user