mirror of
https://github.com/janishutz/libreevent.git
synced 2025-11-25 13:24:24 +00:00
event settings almost done
This commit is contained in:
@@ -52,6 +52,26 @@ class GETHandler {
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'getEvent' ) {
|
||||
db.getJSONDataSimple( 'eventDrafts', query.event ).then( data => {
|
||||
if ( Object.keys( data ).length > 1 ) {
|
||||
resolve( data );
|
||||
} else {
|
||||
reject( { 'code': 404, 'error': 'EventNotFound' } );
|
||||
}
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'getAllEvents' ) {
|
||||
db.getJSONData( 'eventDrafts' ).then( data => {
|
||||
db.getJSONData( 'events' ).then( dat => {
|
||||
resolve( { 'live': dat ?? {}, 'drafts': data ?? {} } );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else {
|
||||
reject( { 'code': 404, 'error': 'Route not found' } );
|
||||
}
|
||||
|
||||
@@ -54,6 +54,52 @@ class POSTHandler {
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'deleteLocation' ) {
|
||||
db.deleteJSONDataSimple( 'locations', data.location ).then( () => {
|
||||
resolve( 'ok' );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'createEvent' ) {
|
||||
db.getJSONDataSimple( 'eventDrafts', data.event ).then( dat => {
|
||||
if ( Object.keys( dat ).length < 1 ) {
|
||||
db.writeJSONDataSimple( 'eventDrafts', data.event, { 'name': 'Unnamed event', 'description': '', 'location': '', 'date': '', 'categories': {}, 'ageGroups': { '1': { 'id': 1, 'name': 'Child', 'age': '0 - 15.99' }, '2': { 'id': 2, 'name': 'Adult' } }, 'maxTickets': 2, 'eventID': data.event } ).then( () => {
|
||||
resolve( 'ok' );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else {
|
||||
reject( { 'code': 409, 'error': 'ExistsAlready' } );
|
||||
}
|
||||
} );
|
||||
} else if ( call === 'saveEvent' ) {
|
||||
db.writeJSONDataSimple( 'eventDrafts', data.event, data.eventData ).then( () => {
|
||||
resolve( 'ok' );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'deployEvent' ) {
|
||||
db.writeJSONDataSimple( 'events', data.event, data.eventData ).then( () => {
|
||||
resolve( 'ok' );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'deleteEvent' ) {
|
||||
db.deleteJSONDataSimple( 'eventDrafts', data.event ).then( () => {
|
||||
db.deleteJSONDataSimple( 'events', data.event ).then( () => {
|
||||
resolve( 'ok' );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'undeployEvent' ) {
|
||||
db.deleteJSONDataSimple( 'events', data.event ).then( () => {
|
||||
resolve( 'ok' );
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'saveTickets' ) {
|
||||
db.writeJSONDataSimple( 'tickets', data.location, data.data ).then( resp => {
|
||||
resolve( resp );
|
||||
|
||||
@@ -36,6 +36,16 @@ class GETHandler {
|
||||
} else {
|
||||
reject( { 'code': 400, 'message': 'Bad request, missing event query' } );
|
||||
}
|
||||
} else if ( call === 'getEvent' ) {
|
||||
db.getJSONDataSimple( 'events', query.event ).then( data => {
|
||||
if ( Object.keys( data ) ) {
|
||||
resolve( data );
|
||||
} else {
|
||||
reject( { 'code': 404, 'error': 'EventNotFound' } );
|
||||
}
|
||||
} ).catch( error => {
|
||||
reject( { 'code': 500, 'error': error } );
|
||||
} );
|
||||
} else if ( call === 'getName' ) {
|
||||
resolve( { 'name': settings.name } );
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,8 @@ class POSTHandler {
|
||||
db.getJSONData( 'booked' ).then( dat => {
|
||||
this.allSelectedSeats = dat;
|
||||
} );
|
||||
// TODO: Load from event db
|
||||
// TODO: Load from event db subtract all occupied seats from the ordered db from it.
|
||||
// TODO: When loading event data, also add currency to it from settings
|
||||
this.ticketTotals = { 'test2': { 'ticket1': 5, 'ticket2': 5 } };
|
||||
this.settings = JSON.parse( fs.readFileSync( path.join( __dirname + '/../../config/settings.config.json' ) ) );
|
||||
}
|
||||
|
||||
1
src/server/backend/db/data/eventDrafts.json
Normal file
1
src/server/backend/db/data/eventDrafts.json
Normal file
@@ -0,0 +1 @@
|
||||
{"test3":{"name":"TestEvent3","description":"Test event to see if it works to save data to backend","location":"test2","date":"2023-08-25","categories":{"1":{"price":{"1":20,"2":30},"bg":"#FFFFFF","fg":"#000000","name":"Category 1","id":"1","ticketCount":1}},"ageGroups":{"1":{"id":1,"name":"Child","age":"0 - 15.99"},"2":{"id":2,"name":"Adult"}},"maxTickets":0,"eventID":"test3","time":"10:00","currency":"USD","isDraft":true}}
|
||||
@@ -152,3 +152,25 @@ module.exports.writeJSONData = ( db, data ) => {
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
module.exports.deleteJSONDataSimple = ( db, identifier ) => {
|
||||
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 ) ?? {};
|
||||
}
|
||||
delete dat[ identifier ];
|
||||
fs.writeFile( path.join( __dirname + '/data/' + db + '.json' ), JSON.stringify( dat ), ( error ) => {
|
||||
if ( error ) {
|
||||
reject( 'Error occurred: Error trace: ' + error );
|
||||
}
|
||||
resolve( true );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
@@ -1,29 +1,25 @@
|
||||
# Account view:
|
||||
- Maybe add multi-language support
|
||||
|
||||
- make pricing groups changeable in UI (event categories)
|
||||
|
||||
- create function that parses DB every 15 minutes and clears out junk --> Also update data in db when user goes to purchase to prevent clearing during purchase
|
||||
|
||||
- Require user to confirm email before purchasing
|
||||
|
||||
- Load all orders of customer from db when selecting tickets and save to memory to check if ticket count has been exceeded or not.
|
||||
|
||||
|
||||
- Guest purchase in the future (maybe remove from matura)
|
||||
|
||||
- Create password changing endpoint (to reset forgotten pwd)
|
||||
- Add Admin profile (page to change account settings per person like changing pwd)
|
||||
- Create function that updates currency for every event when updating currency globally.
|
||||
|
||||
|
||||
- Fix text field overflow (text too big for box)
|
||||
- Other optimisation for seat plan editor
|
||||
- Other optimization for seat plan editor
|
||||
|
||||
|
||||
- Implement Permission system
|
||||
|
||||
- Seat numbering!!
|
||||
|
||||
- Add localization for date
|
||||
|
||||
|
||||
- add webpack (or any other minifying tool) to project website to decrease file size (OPTIONAL)
|
||||
- FUTURE: Add Admin profile (page to change account settings per person like changing pwd)
|
||||
- FUTURE add multi-language support
|
||||
- FUTURE: Guest purchase
|
||||
- FUTURE: add webpack (or any other minifying tool) to project website to decrease file size (OPTIONAL)
|
||||
@@ -11,6 +11,22 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="contentType === 'html'" v-html="data.message" class="options"></div>
|
||||
<div v-else-if="contentType === 'text'" class="options">
|
||||
<h3>{{ data.message }}</h3>
|
||||
<input type="text" v-model="data.selected">
|
||||
<div class="button-wrapper">
|
||||
<button @click="closePopup( 'ok' )" title="Save changes">Save</button>
|
||||
<button @click="closePopup( 'cancel' )" title="Cancel changes">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="contentType === 'number'" class="options">
|
||||
<h3>{{ data.message }}</h3>
|
||||
<input type="number" v-model="data.selected">
|
||||
<div class="button-wrapper">
|
||||
<button @click="closePopup( 'ok' )" title="Save changes">Save</button>
|
||||
<button @click="closePopup( 'cancel' )" title="Cancel changes">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="contentType === 'settings'" class="options">
|
||||
<h3>{{ data.message }}</h3>
|
||||
<settings v-model:settings="data.options"></settings>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useBackendStore = defineStore ( 'backend', {
|
||||
state: () => ( { 'guestPurchase': false, 'guestPurchaseAllowed': true } ),
|
||||
state: () => ( { 'guestPurchase': false, 'guestPurchaseAllowed': false } ),
|
||||
getters: {
|
||||
getVisitedSetupPages: ( state ) => state.visitedSetupPages,
|
||||
getIsGuestPurchase: ( state ) => state.guestPurchase,
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
<div class="side-nav-wrapper">
|
||||
<img src="@/assets/logo.png" alt="libreevent logo" style="width: 80%; margin-left: 10%; margin-bottom: 5%;">
|
||||
<router-link to="/admin" class="admin-menu" @click="navMenu( 'hide' )" title="The home page of the admin panel">Home</router-link>
|
||||
<router-link to="/admin/pages" class="admin-menu" @click="navMenu( 'hide' )" title="Modify your landing page, your terms of service, etc.">Pages</router-link>
|
||||
<router-link to="/admin/events" class="admin-menu" @click="navMenu( 'hide' )" title="Change, view and analyse everything about your events">Events</router-link>
|
||||
<router-link to="/admin/pages" class="admin-menu" @click="navMenu( 'hide' )" title="Modify your landing page">Pages</router-link>
|
||||
<router-link to="/admin/events" class="admin-menu" @click="navMenu( 'hide' )" title="Change and view everything about your events">Events</router-link>
|
||||
<router-link to="/admin/locations" class="admin-menu" @click="navMenu( 'hide' )" title="Change settings about your event locations">Locations</router-link>
|
||||
<router-link to="/admin/plugins" class="admin-menu" @click="navMenu( 'hide' )" title="Install, Uninstall and manage plugins">Plugins</router-link>
|
||||
<router-link to="/admin/plugins" class="admin-menu" @click="navMenu( 'hide' )" title="Manage plugins">Plugins</router-link>
|
||||
<router-link to="/admin/settings" class="admin-menu" @click="navMenu( 'hide' )" title="Change global settings for libreevent">Settings</router-link>
|
||||
<button to="/admin/login" class="admin-menu" @click="logout()" title="Log out of the admin panel">Logout</button>
|
||||
</div>
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
<template>
|
||||
<div class="order">
|
||||
<h2>Events</h2>
|
||||
<button @click="addEvent()">Create new event</button>
|
||||
<div class="order-app" v-if="events">
|
||||
<ul v-for="timeframe in eventList">
|
||||
<h3>{{ timeframe.name }}</h3>
|
||||
<li v-for="event in timeframe.content">
|
||||
<router-link to="/admin/events/view" class="ticket" @click="setActiveTicket( event.eventID );" v-if="new Date( event.date ).getTime() > currentDate">
|
||||
<router-link to="/admin/events/view" class="ticket" @click="setActiveTicket( event.eventID );" v-if="new Date( event.date ).getTime() > currentDate || timeframe.name === 'Drafts'">
|
||||
<div class="ticket-name">
|
||||
<h3>{{ event.name }}</h3>
|
||||
<p>{{ event.description }}</p>
|
||||
@@ -34,8 +35,12 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="order-app" v-else>
|
||||
No future events are available!
|
||||
No events are available!
|
||||
</div>
|
||||
<popups ref="popup" size="big" @data="( data ) => {
|
||||
handleData( data );
|
||||
}"></popups>
|
||||
<rightClickMenu ref="rclk" @command="( command ) => { executeCommand( command ) }"></rightClickMenu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -91,22 +96,32 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import popups from '@/components/notifications/popups.vue';
|
||||
import rightClickMenu from '@/components/settings/rightClickMenu.vue';
|
||||
|
||||
export default {
|
||||
name: 'OrderView',
|
||||
methods: {
|
||||
setActiveTicket ( id ) {
|
||||
sessionStorage.setItem( 'selectedTicket', id );
|
||||
}
|
||||
components: {
|
||||
popups,
|
||||
rightClickMenu,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
events: { 'test':{ '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':'2023-08-15', 'startingPrice':15, 'location': 'TestLocation', 'eventID': 'test', 'currency': 'CHF', 'logo': new URL( '/src/assets/logo.png', import.meta.url ).href }, 'test2':{ 'name': 'TestEvent2', 'description': 'This is a description for the TestEvent to test multiline support and proper positioning of the Fields', 'freeSeats': 2, 'maxSeats': 2, 'date':'2023-06-13', 'startingPrice':15, 'location': 'TestLocation', 'eventID': 'test2', 'currency': 'CHF', 'logo': new URL( '/src/assets/logo.png', import.meta.url ).href } },
|
||||
currentDate: new Date().getTime(),
|
||||
eventList: { 'upcoming': { 'name': 'Upcoming', 'content': {} }, 'past': { 'name': 'Past', 'content': {} } },
|
||||
eventList: { 'upcoming': { 'name': 'Upcoming', 'content': {} }, 'past': { 'name': 'Past', 'content': {} }, 'drafts': { 'name': 'Drafts', 'content': {} } },
|
||||
currentlyOpenMenu: '',
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// Sort events object such that events closest to today are displayed first and past events displayed last
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
loadData () {
|
||||
fetch( '/admin/getAPI/getAllEvents' ).then( res => {
|
||||
res.json().then( dat => {
|
||||
this.events = dat[ 'live' ] ?? {};
|
||||
this.eventList.drafts[ 'content' ] = dat[ 'drafts' ] ?? {};
|
||||
let sortable = [];
|
||||
for ( let event in this.events ) {
|
||||
sortable.push( [ this.events[ event ][ 'eventID' ], new Date( this.events[ event ][ 'date' ] ).getTime() ] );
|
||||
@@ -122,6 +137,77 @@
|
||||
this.eventList.past.content[ sortable[ element ][ 0 ] ] = this.events[ sortable[ element ][ 0 ] ];
|
||||
}
|
||||
}
|
||||
} );
|
||||
} );
|
||||
},
|
||||
openRightClickMenu( id, event ) {
|
||||
this.$refs.rclk.openRightClickMenu( event, { 'edit': { 'command': 'editEvent', 'symbol': 'edit', 'display': 'Edit event' }, 'delete': { 'command': 'deleteEvent', 'symbol': 'delete', 'display': 'Delete event' } } )
|
||||
this.currentlyOpenMenu = id;
|
||||
},
|
||||
executeCommand( command ) {
|
||||
if ( command === 'editEvent' ) {
|
||||
sessionStorage.setItem( 'selectedTicket', this.currentlyOpenMenu );
|
||||
this.$router.push( '/admin/events/view' );
|
||||
} else if ( command === 'deleteEvent' ) {
|
||||
this.$refs.popup.openPopup( 'Do you really want to delete the event ' + this.currentlyOpenMenu + '?', {}, 'confirm' );
|
||||
this.currentPopup = 'delete';
|
||||
}
|
||||
},
|
||||
addEvent () {
|
||||
this.currentPopup = 'add';
|
||||
this.$refs.popup.openPopup( 'Please give the new event a name for internal use', {}, 'text' );
|
||||
},
|
||||
setActiveTicket ( id ) {
|
||||
sessionStorage.setItem( 'selectedTicket', id );
|
||||
},
|
||||
handleData ( data ) {
|
||||
if ( this.currentPopup === 'delete' ) {
|
||||
this.currentPopup = '';
|
||||
if ( data.status === 'ok' ) {
|
||||
delete this.events[ this.currentlyOpenMenu ];
|
||||
}
|
||||
} else if ( this.currentPopup === 'add' ) {
|
||||
if ( data.status === 'ok' ) {
|
||||
const options = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( { 'event': data.data } ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
}
|
||||
};
|
||||
fetch( localStorage.getItem( 'url' ) + '/admin/api/createEvent', options ).then( res => {
|
||||
if ( res.status === 200 ) {
|
||||
res.text().then( () => {
|
||||
this.currentlyOpenMenu = '';
|
||||
this.loadData();
|
||||
} );
|
||||
} else if ( res.status === 409 ) {
|
||||
this.$refs.popup.openPopup( 'This event does already exist. Please choose a different identifier!', {}, 'string' );
|
||||
}
|
||||
} );
|
||||
}
|
||||
} else if ( this.currentPopup === 'delete' ) {
|
||||
if ( data.status === 'ok' ) {
|
||||
const options = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( { 'event': data.data } ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
}
|
||||
};
|
||||
fetch( localStorage.getItem( 'url' ) + '/admin/api/deleteEvent', options ).then( res => {
|
||||
if ( res.status === 200 ) {
|
||||
res.text().then( text => {
|
||||
this.currentlyOpenMenu = '';
|
||||
console.log( text );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<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" @contextmenu="( e ) => { e.preventDefault(); openRightClickMenu( location.locationID, e ); }">
|
||||
<div class="location" @click="selectLocation( location.locationID );" title="Edit this location" @contextmenu="( e ) => { e.preventDefault(); openRightClickMenu( location.locationID, e, location['seatplan-enabled'] ); }">
|
||||
<div class="location-name">
|
||||
<h3>{{ location.locationID }} ({{ location.name }})</h3>
|
||||
<p v-if="location['seatplan-enabled']">This location has a seatplan.</p>
|
||||
@@ -127,8 +127,12 @@
|
||||
}
|
||||
, 'settings' );
|
||||
},
|
||||
openRightClickMenu( id, event ) {
|
||||
openRightClickMenu( id, event, hasSeatplan ) {
|
||||
if ( hasSeatplan ) {
|
||||
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' } } )
|
||||
} else {
|
||||
this.$refs.rclk.openRightClickMenu( event, { 'edit': { 'command': 'editLocation', 'symbol': 'edit', 'display': 'Edit location' }, 'delete': { 'command': 'deleteLocation', 'symbol': 'delete', 'display': 'Delete location' } } )
|
||||
}
|
||||
this.currentlyOpenMenu = id;
|
||||
},
|
||||
executeCommand( command ) {
|
||||
@@ -147,6 +151,21 @@
|
||||
this.currentPopup = '';
|
||||
if ( data.status === 'ok' ) {
|
||||
delete this.locations[ this.currentlyOpenMenu ];
|
||||
const options = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( { 'location': this.currentlyOpenMenu } ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
}
|
||||
};
|
||||
fetch( localStorage.getItem( 'url' ) + '/admin/api/deleteLocation', options ).then( res => {
|
||||
if ( res.status === 200 ) {
|
||||
res.text().then( text => {
|
||||
console.log( text );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
}
|
||||
} else {
|
||||
if ( data.status === 'settings' ) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Settings</h2>
|
||||
<p>Changing any of these settings requires a restart of libreevent.</p>
|
||||
<p>Currency codes used must be valid ISO 4217 codes. Read more on <a href="https://libreevent.janishutz.com/docs/admin-panel/settings#currency" target="_blank">this page</a> of the documentation <!-- https://en.wikipedia.org/wiki/ISO_4217#List_of_ISO_4217_currency_codes"--></p>
|
||||
<settings v-model:settings="settings"></settings>
|
||||
<table class="gateway-settings">
|
||||
@@ -37,7 +38,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<rightClickMenu ref="rclk" @command="( command ) => { executeCommand( command ) }"></rightClickMenu>
|
||||
<popups ref="popup" size="big"></popups>
|
||||
<popups ref="popup" size="big" @data="( data ) => { handlePopupReturns( data ); }"></popups>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -221,8 +222,16 @@
|
||||
this.$refs.popup.openPopup( 'Do you really want to delete the user ' + this.currentlyOpenMenu + '?', {}, 'confirm' );
|
||||
}
|
||||
},
|
||||
handlePopupReturns( message, data ) {
|
||||
|
||||
handlePopupReturns( data ) {
|
||||
console.log( data );
|
||||
if ( data.status === 'cancel' ) {
|
||||
console.log( 'user canceled' );
|
||||
return;
|
||||
} else if ( data.status === 'settings' ) {
|
||||
console.log( 'settings processing' )
|
||||
} else {
|
||||
console.log( 'hi' );
|
||||
}
|
||||
},
|
||||
openRightClickMenu( id, event ) {
|
||||
this.$refs.rclk.openRightClickMenu( event, { 'edit': { 'command': 'openPermissions', 'symbol': 'edit', 'display': 'Edit permissions' }, 'delete': { 'command': 'deleteUser', 'symbol': 'delete', 'display': 'Delete User' } } )
|
||||
|
||||
@@ -10,16 +10,19 @@
|
||||
<template>
|
||||
<div class="details">
|
||||
<h2>{{ event.name }}</h2>
|
||||
<button @click="save( 'draft' )">Save as draft</button>
|
||||
<div class="category-wrapper">
|
||||
<h3>Event Details</h3>
|
||||
<p>Event description</p>
|
||||
<textarea v-model="event.description" class="big-text" cols="70" rows="3" placeholder="Event description..."></textarea>
|
||||
<table class="category">
|
||||
<tr>
|
||||
<td>Event name</td>
|
||||
<td><input type="text" v-model="event.name"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Event location</td>
|
||||
<td v-if="Object.keys( locations ).length > 0">
|
||||
<select v-model="event.location" class="small-text">
|
||||
<option v-for="location in locations" value="location.locationID">{{ location.locationID }} ({{ location.name }})</option>
|
||||
<select v-model="event.location" class="small-text" @change="handleLocationChange()">
|
||||
<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>
|
||||
@@ -33,19 +36,42 @@
|
||||
<router-link to="/admin/ticketEditor">Edit ticket layout</router-link>
|
||||
</tr>
|
||||
</table>
|
||||
<h4>Event description</h4>
|
||||
<textarea v-model="event.description" class="big-text" cols="70" rows="3" placeholder="Event description..."></textarea>
|
||||
</div>
|
||||
<div class="ticket-settings">
|
||||
<!-- TODO: Finish -->
|
||||
<h3>Age Groups</h3>
|
||||
<button @click="addNew( 'ageGroup' )">Add another age group</button>
|
||||
<p>With these settings you can manage and create different age categories which have to be set for every ticket.</p>
|
||||
<div class="category-wrapper">
|
||||
<table class="category" v-for="ageGroup in event.ageGroups">
|
||||
{{ ageGroup.name }} <span class="material-symbols-outlined deleteButton" @click="deleteObject( 'ageGroup', ageGroup.id )" title="Delete age group">delete</span>
|
||||
<tr class="category-details">
|
||||
<td>
|
||||
<div class="category-details">Group name: </div>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" v-model="ageGroup.name">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="category-details">Age (Leave empty for no age info): </div>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" v-model="ageGroup.age">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ticket-settings">
|
||||
<!-- TODO: Load automatically -->
|
||||
<h3>Ticket Settings</h3>
|
||||
<h3>Categories</h3>
|
||||
<button @click="addNew( 'category' )">Add another category</button>
|
||||
<p>The foreground and background colours of the seats are used to show the customer to which category the seats belong.</p>
|
||||
<div class="category-wrapper">
|
||||
<table class="category" v-for="category in event.categories">
|
||||
{{ category.name }}
|
||||
{{ category.name }} <span class="material-symbols-outlined deleteButton" @click="deleteObject( 'category', category.id )" title="Delete category">delete</span>
|
||||
<tr v-for="ageGroup in event.ageGroups">
|
||||
<td>
|
||||
<div class="category-details">{{ ageGroup.name }}<div style="display: inline;" v-if="ageGroup.age"> ({{ ageGroup.age }})</div>:</div>
|
||||
@@ -66,12 +92,18 @@
|
||||
<input type="text" data-coloris v-model="category.bg" onkeydown="return false;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!hasSeatPlan">
|
||||
<td><div class="category-details">Total tickets for this category</div></td>
|
||||
<td>
|
||||
<input type="number" v-model="category.ticketCount">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Assets</h3>
|
||||
<p>Here you can view and change all the marketing images for your event. All assets have to be jpg images.</p>
|
||||
<p>Here you can change all the marketing images for your event. All assets have to be jpg images.</p>
|
||||
<div style="display: flex;">
|
||||
<picture-input
|
||||
ref="logo"
|
||||
@@ -106,15 +138,19 @@
|
||||
<h3>General Settings</h3>
|
||||
<settings v-model:settings="specialSettings"></settings>
|
||||
</div>
|
||||
<button @click="save( 'draft' )">Save as draft</button>
|
||||
<div>
|
||||
<h3>Danger Zone</h3>
|
||||
<button>Go Live</button><button>Delete</button><br><br><br>
|
||||
<!-- TODO: Add functions -->
|
||||
<button @click="dangerZone( 'deploy' )">Go Live</button>
|
||||
<button @click="dangerZone( 'undeploy' )" v-if="hasLiveVersion">Unpublish event</button>
|
||||
<button @click="dangerZone( 'delete' )">Delete Event</button>
|
||||
<br><br><br>
|
||||
</div>
|
||||
<!-- <div>
|
||||
<p>Please read the documentation of this section if you want to use the requirements. It requires specific syntax to work. See <a href="https://libreevent.janishutz.com/docs/admin-panel/events#special-requirements" target="_blank">here</a> for more information</p>
|
||||
</div> -->
|
||||
<notifications ref="notification" location="topright"></notifications>
|
||||
<popups ref="popups" size="normal" @data="( data ) => { handlePopup( data ) }"></popups>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -143,16 +179,19 @@
|
||||
.category-details {
|
||||
margin-left: 7%;;
|
||||
}
|
||||
|
||||
.deleteButton {
|
||||
cursor: pointer;
|
||||
font-size: 110%;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import settings from '@/components/settings/settings.vue';
|
||||
import notifications from '@/components/notifications/notifications.vue';
|
||||
import popups from '@/components/notifications/popups.vue';
|
||||
import PictureInput from 'vue-picture-input';
|
||||
|
||||
// TODO: When loading data form server, also load categories of this seat plan
|
||||
// and from it construct the array, if not set already.
|
||||
|
||||
// TODO: Save info if draft or live
|
||||
|
||||
export default {
|
||||
@@ -161,11 +200,12 @@
|
||||
settings,
|
||||
notifications,
|
||||
PictureInput,
|
||||
popups,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
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 },
|
||||
event: { 'name': 'Unnamed event', 'description': '', 'location': '', 'date': '', 'categories': {}, 'ageGroups': { '1':{ 'id': 1, 'name':'Child', 'age':'0 - 15.99' }, '2':{ 'id': 2, 'name': 'Adult' } }, 'maxTickets': 2, 'eventID': 'untitled' },
|
||||
specialSettings: {
|
||||
// 'guest-purchase': {
|
||||
// 'display': 'Enable guest purchase',
|
||||
@@ -224,7 +264,13 @@
|
||||
// 'value': '',
|
||||
// 'type': 'text',
|
||||
// },
|
||||
}
|
||||
},
|
||||
command: '',
|
||||
currentLocation: '',
|
||||
toDelete: '',
|
||||
currency: 'USD',
|
||||
hasLiveVersion: false,
|
||||
hasSeatPlan: true,
|
||||
}
|
||||
},
|
||||
created () {
|
||||
@@ -235,12 +281,23 @@
|
||||
fetch( localStorage.getItem( 'url' ) + '/admin/getAPI/getLocations' ).then( res => {
|
||||
res.json().then( data => {
|
||||
this.locations = data;
|
||||
fetch( localStorage.getItem( 'url' ) + '/admin/getAPI/getEvent?event=' + this.eventID ).then( res => {
|
||||
if ( res.status === 200 ) {
|
||||
res.json().then( data => {
|
||||
this.event = data;
|
||||
this.currentLocation = this.event.location;
|
||||
this.hasSeatPlan = this.locations[ this.event.location ] ? ( this.locations[ this.event.location ][ 'seatplan-enabled' ] ?? false ) : false;
|
||||
} ).catch( error => {
|
||||
console.error( error );
|
||||
} );
|
||||
} else if ( res.status === 404 ) {
|
||||
this.$router.push( '/admin/events' );
|
||||
}
|
||||
} );
|
||||
} ).catch( error => {
|
||||
console.error( error );
|
||||
} );
|
||||
} );
|
||||
|
||||
// TODO: Load data from server
|
||||
},
|
||||
methods: {
|
||||
saveImages() {
|
||||
@@ -263,11 +320,188 @@
|
||||
this.$refs.notification.createNotification( 'No image selected!', 5, 'error', 'normal' );
|
||||
}
|
||||
},
|
||||
loadLocationData() {
|
||||
|
||||
save ( action ) {
|
||||
if ( Object.keys( this.event.ageGroups ).length > 0 && Object.keys( this.event.categories ).length > 0 ) {
|
||||
for ( let ageGroup in this.event.ageGroups ) {
|
||||
if ( this.event.ageGroups[ ageGroup ].name == '' ) {
|
||||
this.$refs.popups.openPopup( 'One or more age groups are missing their names. Please ensure that all age groups have a name and try again!', {}, 'string' );
|
||||
return;
|
||||
}
|
||||
}
|
||||
for ( let category in this.event.categories ) {
|
||||
for ( let price in this.event.categories[ category ].price ) {
|
||||
if ( this.event.categories[ category ].price[ price ] < 0.5 || ( !this.event.categories[ category ].ticketCount && this.hasSeatPlan ) ) {
|
||||
this.$refs.popups.openPopup( 'At least one of the prices for at least one of the categories is below the minimum of ' + this.currency + ' 0.5', {}, 'string' );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.event[ 'currency' ] = this.currency;
|
||||
this.event[ 'isDraft' ] = true;
|
||||
this.event.maxTickets = this.specialSettings[ 'maxTickets' ].value;
|
||||
let url = localStorage.getItem( 'url' ) + '/admin/api/saveEvent';
|
||||
if ( action === 'deploy' ) {
|
||||
url = localStorage.getItem( 'url' ) + '/admin/api/deployEvent';
|
||||
}
|
||||
const options = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( { 'event': this.event.eventID, 'eventData': this.event } ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
}
|
||||
};
|
||||
fetch( url, options ).then( res => {
|
||||
if ( res.status === 200 ) {
|
||||
if ( action === 'deploy' ) {
|
||||
this.$refs.notification.createNotification( 'Your event has been set to be live successfully!', 5, 'ok', 'normal' );
|
||||
this.hasLiveVersion = true;
|
||||
} else {
|
||||
this.$refs.notification.createNotification( 'Saved as draft successfully!', 5, 'ok', 'normal' );
|
||||
}
|
||||
}
|
||||
} );
|
||||
} else {
|
||||
this.$refs.popups.openPopup( 'Please ensure that you have at least one age group and one category defined!', {}, 'string' );
|
||||
}
|
||||
},
|
||||
save () {
|
||||
|
||||
addNew( type ) {
|
||||
if ( type === 'ageGroup' ) {
|
||||
this.$refs.popups.openPopup( 'Choose a name for the age group', {}, 'text' );
|
||||
this.command = 'addAgeGroup';
|
||||
} else if ( type === 'category' ) {
|
||||
this.$refs.popups.openPopup( 'Choose a name for the new category', {}, 'number', Object.keys( this.event.categories ).length + 1 );
|
||||
this.command = 'addCategory';
|
||||
}
|
||||
},
|
||||
deleteObject( type, data ) {
|
||||
if ( type === 'ageGroup' ) {
|
||||
this.$refs.popups.openPopup( 'Do you really want to delete this age group?', {}, 'confirm' );
|
||||
this.command = 'deleteAgeGroup';
|
||||
this.toDelete = data;
|
||||
} else if ( type === 'category' ) {
|
||||
this.$refs.popups.openPopup( 'Do you really want to delete this category', {}, 'confirm' );
|
||||
this.command = 'deleteCategory';
|
||||
this.toDelete = data;
|
||||
}
|
||||
},
|
||||
handleLocationChange() {
|
||||
if ( Object.keys( this.event.categories ).length > 1 && this.locations[ this.event.location ][ 'seatplan-enabled' ] ) {
|
||||
this.command = 'locationChange';
|
||||
this.$refs.popups.openPopup( 'You have edited the categories of this location. Changing the location now leads to data loss.', {}, 'confirm' );
|
||||
} else {
|
||||
this.command = 'locationChange';
|
||||
this.handlePopup( { 'status': 'ok' } );
|
||||
}
|
||||
},
|
||||
handlePopup( data ) {
|
||||
if ( data.status === 'ok' ) {
|
||||
if ( this.command === 'addCategory' ) {
|
||||
this.command = '';
|
||||
if ( !this.event.categories[ data.data ] ) {
|
||||
this.event.categories[ data.data ] = { 'price': {}, 'bg': '#FFFFFF', 'fg': '#000000', 'name': 'Category ' + data.data, 'id': data.data, 'ticketCount': 1 };
|
||||
for ( let ageGroup in this.event.ageGroups ) {
|
||||
this.event.categories[ data.data ][ 'price' ][ ageGroup ] = 0;
|
||||
}
|
||||
} else {
|
||||
this.$refs.popups.openPopup( 'That category already exists!', {}, 'string' );
|
||||
}
|
||||
} else if ( this.command === 'addAgeGroup' ) {
|
||||
this.command = '';
|
||||
for ( let ageGroup in this.event.ageGroups ) {
|
||||
if ( this.event.ageGroups[ ageGroup ].name === data.data ) {
|
||||
this.$refs.popups.openPopup( 'That age group exists already', {}, 'string' );
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.event.ageGroups[ Object.keys( this.event.categories ).length + 1 ] = { 'id': Object.keys( this.event.categories ).length + 1, 'name': data.data };
|
||||
for ( let ageGroup in this.event.ageGroups ) {
|
||||
for ( let category in this.event.categories ) {
|
||||
if ( !this.event.categories[ category ][ 'price' ][ ageGroup ] ) {
|
||||
this.event.categories[ category ][ 'price' ][ ageGroup ] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ( this.command === 'deleteCategory' ) {
|
||||
delete this.event.categories[ this.toDelete ];
|
||||
} else if ( this.command === 'deleteAgeGroup' ) {
|
||||
delete this.event.ageGroups[ this.toDelete ];
|
||||
} else if ( this.command === 'locationChange' ) {
|
||||
this.currentLocation = this.event.location;
|
||||
if ( this.locations[ this.event.location ][ 'seatplan-enabled' ] ) {
|
||||
fetch( '/admin/getAPI/getSeatplan?location=' + this.event.location ).then( res => {
|
||||
if ( res.status === 200 ) {
|
||||
res.json().then( json => {
|
||||
this.hasSeatPlan = this.locations[ this.event.location ][ 'seatplan-enabled' ] ?? false;
|
||||
this.event.categories = {};
|
||||
for ( let element in json.data ) {
|
||||
if ( json.data[ element ][ 'type' ] === 'seat' || json.data[ element ][ 'type' ] === 'stand' ) {
|
||||
this.event.categories[ json.data[ element ][ 'category' ] ] = { 'price': {}, 'bg': '#FFFFFF', 'fg': '#000000', 'name': 'Category ' + json.data[ element ][ 'category' ], 'id': json.data[ element ][ 'category' ], 'ticketCount': 1 };
|
||||
for ( let ageGroup in this.event.ageGroups ) {
|
||||
this.event.categories[ json.data[ element ][ 'category' ] ][ 'price' ][ ageGroup ] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
} )
|
||||
}
|
||||
} else if ( this.command === 'deployEvent' ) {
|
||||
this.save( 'deploy' );
|
||||
} else if ( this.command === 'undeployEvent' ) {
|
||||
const options = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( { 'event': sessionStorage.getItem( this.event.eventID ) } ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
}
|
||||
};
|
||||
fetch( url, options ).then( res => {
|
||||
if ( res.status === 200 ) {
|
||||
this.hasLiveVersion = false;
|
||||
this.$refs.notification.createNotification( 'Your event is no longer publicly visible!', 5, 'ok', 'normal' );
|
||||
} else {
|
||||
this.$refs.notification.createNotification( 'There was an error hiding your event', 5, 'error', 'normal' );
|
||||
}
|
||||
} );
|
||||
} else if ( this.command === 'deleteEvent' ) {
|
||||
const options = {
|
||||
method: 'post',
|
||||
body: JSON.stringify( { 'event': sessionStorage.getItem( this.event.eventID ) } ),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'charset': 'utf-8'
|
||||
}
|
||||
};
|
||||
fetch( url, options ).then( res => {
|
||||
if ( res.status === 200 ) {
|
||||
this.$refs.notification.createNotification( 'Your event has been deleted successfully!', 5, 'ok', 'normal' );
|
||||
setTimeout( () => {
|
||||
this.$router.push( '/admin/events' );
|
||||
}, 5000 );
|
||||
} else {
|
||||
this.$refs.notification.createNotification( 'There was an error deleting your event', 5, 'error', 'normal' );
|
||||
}
|
||||
} );
|
||||
}
|
||||
} else if ( data.status === 'cancel' ) {
|
||||
if ( this.command === 'locationChange' ) {
|
||||
this.event.location = this.currentLocation;
|
||||
}
|
||||
}
|
||||
},
|
||||
dangerZone( action ) {
|
||||
if ( action === 'deploy' ) {
|
||||
this.$refs.popups.openPopup( 'Do you really want to go live with this event?', {}, 'confirm' );
|
||||
this.command = 'deployEvent';
|
||||
} else if ( action === 'undeploy' ) {
|
||||
this.$refs.popups.openPopup( 'Do you really want to remove this event from the event listings?', {}, 'confirm' );
|
||||
this.command = 'undeployEvent';
|
||||
} else if ( type === 'delete' ) {
|
||||
this.$refs.popups.openPopup( 'Do you really want to delete this event? This action cannot be undone', {}, 'confirm' );
|
||||
this.command = 'deleteEvent';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user