Commit e2d23fb2 authored by jackie / Andrea Ida Malkah Klaura's avatar jackie / Andrea Ida Malkah Klaura
Browse files

Merge branch 'feature-showmanager-superuser' into develop

parents 2a737e6e 69a6c044
This diff is collapsed.
......@@ -34,6 +34,7 @@ export default {
{ slug: 'home', title: 'Home' },
{ slug: 'shows', title: 'Sendungen verwalten' },
{ slug: 'files', title: 'Dateien und Playlists' },
{ slug: 'emissions', title: 'Sendezeiten'},
{ slug: 'settings', title: 'Settings' },
{ slug: 'credits', title: 'Credits' },
{ slug: 'debug', title: 'Debug' }
......
<template>
<b-container>
<b-row v-if="loaded.shows">
<b-col>
<h3>{{ shows[currentShow].name }}</h3>
</b-col>
<b-col align="right">
<b-dropdown
id="ddshows"
text="Sendereihe auswählen"
variant="outline-info"
>
<b-dropdown-item
v-for="(show, index) in shows"
:key="show.id"
@click="switchShow(index)"
>
{{ show.name }}
</b-dropdown-item>
</b-dropdown>
</b-col>
</b-row>
<b-row v-else>
<b-col cols="12">
<div align="center">
... loading show data ...
</div>
</b-col>
</b-row>
<hr>
<b-alert
variant="danger"
:show="conflictMode"
>
<b-row>
<b-col cols="12">
<div align="center">
<h4>Conflict Resolution</h4>
</div>
</b-col>
<b-col>
... coming soon ...
</b-col>
</b-row>
</b-alert>
<full-calendar
ref="calendar"
editable="false"
default-view="agendaWeek"
:events="calendarSlots"
:config="calendarConfig"
@view-render="renderView"
@event-selected="eventSelected"
@event-drop="eventDrop"
@event-resize="eventResize"
@event-created="eventCreated"
/>
<app-modalEmissionManagerCreate
ref="appModalEmissionManagerCreate"
/>
</b-container>
</template>
<script>
import axios from 'axios'
import { FullCalendar } from 'vue-full-calendar'
import 'fullcalendar/dist/fullcalendar.css'
import modalEmissionManagerCreate from './EmissionManagerModalCreate.vue'
export default {
components: {
FullCalendar,
'app-modalEmissionManagerCreate': modalEmissionManagerCreate,
},
data () {
return {
currentShow: 0,
shows: [],
timeslots: [],
calendarSlots: [],
// flags for loading data
loaded: {
shows: false,
timeslots: false,
calendarSlots: false,
},
// this flag signifies if we are in conflict resolution mode
conflictMode: false,
// this is the whole configuration for our schedule calendar, including
// simple event handlers that do not need the whole components scope
calendarConfig: {
height: 600,
firstDay: 1,
header: {
left: 'title',
center: '',
right: 'today prev,next'
},
views: {
agendaWeek: {
columnHeaderFormat: 'ddd D.M.',
timeFormat: 'k:mm',
slotLabelFormat: 'k:mm',
allDaySlot: false,
},
},
// here we add a simple tooltip to every event, so that the full title
// of a show can be viewed
eventRender: function(event, element) {
element.attr('title', event.title);
},
},
}
},
created () {
if (this.$route.query.show) {
this.currentShow = this.$route.query.show
} else {
this.currentShow = 0
}
this.loadShows()
},
methods: {
switchShow (index) {
this.currentShow = index
this.loadCalendarSlots()
},
getShowTitleById (id) {
let i = this.shows.findIndex(show => show.id === id)
if (i >= 0) {
return this.shows[i].name
} else {
return 'Error: no show found for this timeslot'
}
},
eventSelected (event, jsEvent, view) {
this.$log.debug('eventSelected', event, jsEvent, view)
},
eventDrop (event) {
this.$log.debug('eventDrop', event)
},
eventResize (event) {
this.$log.debug('eventResize', event)
},
eventCreated (event) {
this.$refs.appModalEmissionManagerCreate.open(event.start, event.end)
},
// this is called when the user changes the calendar view, so we just
// refetch the timeslots with the updated visible date range
renderView (view) {
if (this.loaded.shows) {
let start = null
let end = null
// in case it gets called from a modal, we use the current view
// otherwise we use the new dates from the view received by the renderView event
if (view === null) {
start = this.$refs.calendar.fireMethod('getView').start.format()
end = this.$refs.calendar.fireMethod('getView').end.format()
} else {
start = view.start.format()
end = view.end.format()
}
// if we are in conflict resolution mode we do not load all timeslots
// but only the conflicting ones
if (this.conflictMode) {
this.loadConflictSlots(start, end)
} else {
this.loadTimeslots(start, end)
}
}
},
resolve (data) {
this.$log.debug('resolve', data)
this.conflictMode = true
},
loadCalendarSlots () {
this.loaded.calendarSlots = false
this.calendarSlots = []
for (let i in this.timeslots) {
let highlighting = 'otherShow'
if (this.timeslots[i].show === this.shows[this.currentShow].id) {
highlighting = 'currentShow'
}
this.calendarSlots.push({
start: this.timeslots[i].start,
end: this.timeslots[i].end,
title: this.getShowTitleById(this.timeslots[i].show),
className: highlighting
})
}
this.loaded.calendarSlots = true
},
loadTimeslots (start, end) {
this.$log.debug('loadTimeslots: currentShow = '+this.currentShow)
this.loaded.timeslots = false
let uri = process.env.VUE_APP_API_STEERING + 'timeslots?start=' + start + '&end=' + end
axios.get(uri, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
}).then(response => {
this.timeslots = response.data
this.loaded.timeslots = true
this.loadCalendarSlots()
}).catch(error => {
this.$log.error(error.response.status + ' ' + error.response.statusText)
this.$log.error(error.response)
alert('Error: could not load timeslots. See console for details.')
})
},
loadShows () {
this.loaded.shows = false
let uri = process.env.VUE_APP_API_STEERING + 'shows'
axios.get(uri, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
}).then(response => {
this.shows = response.data
this.loaded.shows = true
let start = this.$refs.calendar.fireMethod('getView').start.format()
let end = this.$refs.calendar.fireMethod('getView').end.format()
this.loadTimeslots(start, end)
}).catch(error => {
this.$log.error(error.response.status + ' ' + error.response.statusText)
this.$log.error(error.response)
alert('Error: could not load shows. See console for details.')
})
},
updateSchedules () {
this.$log.debug(this.$refs.calendar.fireMethod('getView').start.format())
}
},
}
</script>
<style>
.otherShow {
background-color: #eee;
}
a.currentShow {
background-color: #17a2b8;
}
</style>
<template>
<div>
<b-modal
ref="modalEmissionManagerCreate"
title="Create a new schedule"
size="lg"
@ok="create"
>
<b-row>
<b-col cols="3">
Creating schedule for:
</b-col>
<b-col cols="9">
<b v-if="$parent.loaded.shows">
{{ $parent.shows[$parent.currentShow].name }}
</b>!
</b-col>
</b-row>
<b-alert
variant="warning"
:show="pastEventWarning"
>
Past events will be ignored. Start date was set to today!<br>
Try again or change the start date to something in the future.
</b-alert>
<div v-if="loaded">
<b-row>
<b-col cols="2">
Start:
</b-col>
<b-col cols="3">
<b-form-input
v-model="valuePick.dstart"
type="date"
/>
</b-col>
<b-col cols="3">
<b-form-input
v-model="valuePick.tstart"
type="time"
/>
</b-col>
<b-col cols="1">
End:
</b-col>
<b-col cols="3">
<b-form-input
v-model="valuePick.tend"
type="time"
/>
</b-col>
</b-row>
<b-row>
<b-col cols="2">
Type of event:
</b-col>
<b-col cols="6">
<b-form-select
v-model="valuePick.rrule"
:options="rruleOptions"
/>
</b-col>
<b-col v-if="valuePick.rrule != 1">
Until:
</b-col>
<b-col v-if="valuePick.rrule != 1">
<b-form-input
v-model="valuePick.until"
type="date"
/>
</b-col>
</b-row>
</div>
</b-modal>
</div>
</template>
<script>
import axios from 'axios'
import prettyDate from '../mixins/prettyDate'
export default {
mixins: [ prettyDate ],
data () {
return {
newSchedule: null,
loaded: false,
pastEventWarning: false,
valuePick: {
dstart: null,
tstart: null,
tend: null,
until: null,
rrule: 1
},
rruleOptions: [
{ value: 1, text: 'einmalig' },
{ value: 2, text: 'täglich' },
{ value: 3, text: 'werktäglich' },
{ value: 4, text: 'wöchentlich' },
{ value: 5, text: 'zweiwöchentlich' },
{ value: 6, text: 'vierwöchentlich' },
{ value: 7, text: 'gerade Kalenderwoche' },
{ value: 8, text: 'ungerade Kalenderwoche' },
{ value: 9, text: 'Jede 1. Woche im Monat' },
{ value: 10, text: 'Jede 2. Woche im Monat' },
{ value: 11, text: 'Jede 3. Woche im Monat' },
{ value: 12, text: 'Jede 4. Woche im Monat' },
{ value: 13, text: 'Jede 5. Woche im Monat' },
],
}
},
methods: {
create (event) {
// prevent the modal from closing automatically on click
event.preventDefault()
// check for past dates; as past dates will be ignored by steering we
// want to make the user aware - otherwise it might get confusing in
// conflict resolution
let now = this.apiDate(new Date())
if (this.valuePick.dstart < now) {
this.valuePick.dstart = now
this.pastEventWarning = true
return
} else {
this.pastEventWarning = false
}
// take all values that have been picked and put them into our new
// schedule. so far we do not need any transformations.
this.newSchedule.schedule.dstart = this.valuePick.dstart
this.newSchedule.schedule.tstart = this.valuePick.tstart
this.newSchedule.schedule.tend = this.valuePick.tend
this.newSchedule.schedule.until = this.valuePick.until
this.newSchedule.schedule.rrule = this.valuePick.rrule
// now generate the URL and POST our new schedule
let uri = process.env.VUE_APP_API_STEERING_SHOWS + this.$parent.shows[this.$parent.currentShow].id + '/schedules/'
axios.post(uri, this.newSchedule, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
// we have to check if there are any conflicts with existing timeslots
let conflicts = false
for (let i in response.data.projected) {
if (response.data.projected[i].collisions.length > 0) {
conflicts = true
break
}
}
// if there are no conflicts we can set an empty solutions object
// in our newSchedule and submit it.
if (!conflicts) {
this.newSchedule.solutions = {}
this.submit()
// otherwise we have to resolve the conflict first.
} else {
this.$parent.resolve(response.data)
this.$refs.modalEmissionManagerCreate.hide()
}
}).catch(error => {
this.$log.error(error.response.status + ' ' + error.response.statusText)
this.$log.error(error.response)
alert('Error: could not create schedule. See console for details.')
// and we leave the modal open, so no call to its .hide function here
})
},
submit () {
let uri = process.env.VUE_APP_API_STEERING_SHOWS + this.$parent.shows[this.$parent.currentShow].id + '/schedules/'
axios.post(uri, this.newSchedule, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
// if for some reason a new conflict arose, e.g. because in the meantime
// someone else inserted a conflicting schedule, we have to resolve.
// TODO: based on single event schedule
// TODO: check for complex schedules with resolved conflicts
if (response.data.projected === undefined) {
this.$parent.renderView(null)
} else {
this.$parent.resolve(response.data)
}
this.$refs.modalEmissionManagerCreate.hide()
}).catch(error => {
this.$log.error(error.response.status + ' ' + error.response.statusText)
this.$log.error(error.response)
alert('Error: could not submit final schedule. See console for details.')
// and we leave the modal open, so no call to its .hide function here
})
},
// initialise a new schedule and open the modal
open (start, end) {
let dstart = start.format('YYYY-MM-DD')
let tstart = start.format('HH:mm')
let tend = end.format('HH:mm')
let until = end.format('YYYY-MM-DD')
let now = this.apiDate(new Date())
if (dstart < now) {
dstart = now
this.pastEventWarning = true
} else {
this.pastEventWarning = false
}
this.valuePick.dstart = dstart
this.valuePick.tstart = tstart
this.valuePick.tend = tend
this.valuePick.until = until
this.newSchedule = {
schedule: {
rrule: 1,
show: 21,
byweekday: 0,
dstart: dstart,
tstart: tstart,
tend: tend,
until: until,
is_repetition: false,
add_days_no: 0,
add_business_days_only: false,
fallback_id: 0,
automation_id: 0,
}
}
this.loaded = true
this.$refs.modalEmissionManagerCreate.show()
},
}
}
</script>
<style scoped>
.row {
margin-bottom: 1em;
}
</style>
......@@ -10,6 +10,7 @@
<b-button
v-if="$parent.user.steeringUser.is_superuser"
v-b-popover.hover.top="'Add a new show'"
variant="info"
@click="$refs.appModalSuperuser.showModalAddShow()"
>
+
......@@ -18,7 +19,7 @@
<b-dropdown
id="ddshows"
text="Sendereihe auswählen"
variant="info"
variant="outline-info"
>
<b-dropdown-item
v-for="(show, index) in shows"
......@@ -79,6 +80,7 @@
<b-button
v-if="$parent.user.steeringUser.is_superuser"
variant="danger"
size="sm"
@click="$refs.appModalSuperuser.showModalDeleteShow(shows[currentShow].id, shows[currentShow].name)"
>
Delete show
......@@ -116,9 +118,23 @@
<!-- here are the filter settings for our timeslots table -->
<b-card>
<b-btn v-b-toggle.timeslotFilterCollapse>
Toggle timeslot filters
</b-btn>
<b-row>
<b-col>
<b-btn v-b-toggle.timeslotFilterCollapse>
Toggle timeslot filters
</b-btn>
</b-col>
<b-col align="right">
<b-button
v-if="$parent.user.steeringUser.is_superuser"
variant="info"
@click="$router.push({path: 'emissions', query: { show: currentShow }})"
>
Switch to Emission Manager
</b-button>
</b-col>
</b-row>
<b-collapse id="timeslotFilterCollapse">
<br>