Newer
Older
<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"
>
<div
v-if="conflictMode"
align="center"
>
<h4>Conflict Resolution</h4>
<p>for new schedule</p>
<p>
from <b>{{ resolveData.schedule.dstart }}, {{ resolveData.schedule.tstart }}</b>
to <b>{{ resolveData.schedule.tend }}</b>.
</p>
<p v-if="resolveData.schedule.rrule !== 1">
This is a recurring event: <b>{{ rruleRender(resolveData.schedule.rrule) }}</b>,
until: <b>{{ resolveData.schedule.until }}</b>.
</p>
<p v-if="conflictCount > 0">
Conflicts to resolve: {{ conflictCount }}
</p>
<p v-else>
<b-button
variant="success"
@click="resolveSubmit"
>
0 conflicts left! Submit this solution.
</b-button>
</p>
</div>
<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"
<app-modalEmissionManagerResolve
ref="appModalEmissionManagerResolve"
/>
</b-container>
</template>
<script>
import axios from 'axios'
import { FullCalendar } from 'vue-full-calendar'
import 'fullcalendar/dist/fullcalendar.css'
import modalEmissionManagerCreate from './EmissionManagerModalCreate.vue'
import modalEmissionManagerResolve from './EmissionManagerModalResolve.vue'
import rrules from '../mixins/rrules'
export default {
components: {
FullCalendar,
'app-modalEmissionManagerCreate': modalEmissionManagerCreate,
'app-modalEmissionManagerResolve': modalEmissionManagerResolve,
mixins: [ rrules ],
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,
// when conflict mode is activated, this should hold the steering response
// from schedule creation, with all the conflicts and solutions
resolveData: null,
conflictCount: 0,
conflictSolutions: [],
// 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) {
if (this.conflictMode) {
if (event.hash === undefined) {
return
} else if (this.conflictSolutions[event.hash] === undefined) {
this.$refs.appModalEmissionManagerResolve.openNotNeeded()
} else {
this.$refs.appModalEmissionManagerResolve.open(event)
}
}
},
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()
}
// we only load new timeslots, if we are not in conflict mode
if (!this.conflictMode) {
this.loadTimeslots(start, end)
}
}
},
resolve (data) {
this.$log.debug('resolve', data)
this.resolveData = data
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
this.conflictCount = 0
this.conflictSolutions = data.solutions
this.calendarSlots = []
try {
for (let i in data.projected) {
let newSlot = {
// we need a numeric ID for the event for later selection by the user.
// with converting the hash to a number (in this case a float), we
// do not risk using a number that is already used by a timeslot id
// of a conflicting timeslot
id: Number(data.projected[i].hash),
// the hash is needed to compare against solutions and conflicts
hash: data.projected[i].hash,
start: data.projected[i].start,
end: data.projected[i].end,
title: 'new',
collisions: [],
solutionChoices: [],
className: 'noconflict',
editable: false,
}
if (data.projected[i].collisions.length > 0) {
newSlot.className = 'conflict'
newSlot.solutionChoices = data.projected[i].solution_choices
for (let col of data.projected[i].collisions) {
let conflictingSlot = {
id: col.id,
start: col.start,
end: col.end,
title: col.show_name,
className: 'otherShow',
editable: false,
}
this.calendarSlots.push(conflictingSlot)
this.conflictCount++
newSlot.collisions.push(col)
}
}
this.calendarSlots.push(newSlot)
}
} catch (err) {
this.$log.error(err)
}
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
resolveEvent (toResolve, mode) {
this.$log.debug('resolveEvent', toResolve)
this.conflictCount -= toResolve.collisions.length
switch (mode) {
case 'theirs':
this.conflictSolutions[toResolve.hash] = 'theirs'
toResolve.className = 'discarded'
break
default:
this.$log.error('EmissionManager.resolveEvent')
this.$log.error('toResolve:', toResolve)
this.$log.error('mode:', mode)
alert('Error: an undefined conflict resolution mode was chosen. See console for details')
break
}
},
// submit a conflict-resolved schedule to steering
resolveSubmit () {
// TODO: check why steering retourns undefined and null values here
if (this.resolveData.schedule.add_business_days_only === undefined) { this.resolveData.schedule.add_business_days_only = false }
if (this.resolveData.schedule.add_days_no === null) { this.resolveData.schedule.add_days_no = 0 }
if (this.resolveData.schedule.is_repetition === undefined) { this.resolveData.schedule.is_repetition = false }
if (this.resolveData.schedule.fallback_id === null) { this.resolveData.schedule.fallback_id = 0 }
if (this.resolveData.schedule.automation_id === null) { this.resolveData.schedule.automation_id = 0 }
if (this.resolveData.schedule.byweekday === undefined) { this.resolveData.schedule.byweekday = 0 }
// create the resolved schedule object including solutions
let resolvedSchedule = {
schedule: this.resolveData.schedule,
solutions: this.resolveData.solutions,
}
this.$log.debug('resolveSubmit: schedule:', resolvedSchedule)
// now generate the URL and POST it to steering
let uri = process.env.VUE_APP_API_STEERING_SHOWS + this.shows[this.currentShow].id + '/schedules/'
axios.post(uri, resolvedSchedule, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
}).then(response => {
this.$log.debug('resolveSubmit: response:', 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.
if (response.data.projected === undefined) {
this.conflictMode = false
this.renderView(null)
} else {
this.resolve(response.data)
}
}).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
})
},
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.')
})
},
},
}
</script>
<style>
.otherShow {
background-color: #eee;
}
a.currentShow {
background-color: #17a2b8;
}
.conflict {
background-color: #b00;
}
.noconflict {
background-color: #17a2b8;
}
.discarded {
background-color: #eee;
text-decoration: line-through;
}