mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2024-12-04 15:24:23 +01:00
404 lines
14 KiB
Plaintext
404 lines
14 KiB
Plaintext
'use strict'
|
|
|
|
##
|
|
# Controller used in the calendar management page
|
|
##
|
|
|
|
Application.Controllers.controller "AdminCalendarController", ["$scope", "$state", "$uibModal", "moment", "Availability", 'Slot', 'Setting', 'growl', 'dialogs', 'availabilitiesPromise', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', '_t'
|
|
($scope, $state, $uibModal, moment, Availability, Slot, Setting, growl, dialogs, availabilitiesPromise, bookingWindowStart, bookingWindowEnd, machinesPromise, _t) ->
|
|
|
|
|
|
|
|
### PRIVATE STATIC CONSTANTS ###
|
|
|
|
# The calendar is divided in slots of 30 minutes
|
|
BASE_SLOT = '00:30:00'
|
|
|
|
# The bookings can be positioned every half hours
|
|
BOOKING_SNAP = '00:30:00'
|
|
|
|
# The calendar will be initialized positioned under 9:00 AM
|
|
DEFAULT_CALENDAR_POSITION = '09:00:00'
|
|
|
|
# We do not allow the creation of slots that are not a multiple of 60 minutes
|
|
SLOT_MULTIPLE = 60
|
|
|
|
|
|
|
|
### PUBLIC SCOPE ###
|
|
|
|
## list of the FabLab machines
|
|
$scope.machines = machinesPromise
|
|
|
|
## currently selected availability
|
|
$scope.availability = null
|
|
|
|
## bind the availabilities slots with full-Calendar events
|
|
$scope.eventSources = []
|
|
$scope.eventSources.push
|
|
events: availabilitiesPromise
|
|
textColor: 'black'
|
|
|
|
## after fullCalendar loads, provides access to its methods through $scope.calendar.fullCalendar()
|
|
$scope.calendar = null
|
|
|
|
## fullCalendar (v2) configuration
|
|
$scope.calendarConfig =
|
|
timezone: Fablab.timezone
|
|
lang: Fablab.fullcalendar_locale
|
|
header:
|
|
left: 'month agendaWeek'
|
|
center: 'title'
|
|
right: 'today prev,next'
|
|
firstDay: 1 # Week start on monday (France)
|
|
scrollTime: DEFAULT_CALENDAR_POSITION
|
|
slotDuration: BASE_SLOT
|
|
snapDuration: BOOKING_SNAP
|
|
allDayDefault: false
|
|
minTime: "00:00:00"
|
|
maxTime: "24:00:00"
|
|
height: 'auto'
|
|
buttonIcons:
|
|
prev: 'left-single-arrow'
|
|
next: 'right-single-arrow'
|
|
timeFormat:
|
|
agenda:'H:mm'
|
|
month: 'H(:mm)'
|
|
axisFormat: 'H:mm'
|
|
|
|
allDaySlot: false
|
|
defaultView: 'agendaWeek'
|
|
selectable: true
|
|
selecHelper: true
|
|
select: (start, end, jsEvent, view) ->
|
|
calendarSelectCb(start, end, jsEvent, view)
|
|
eventClick: (event, jsEvent, view)->
|
|
calendarEventClickCb(event, jsEvent, view)
|
|
eventRender: (event, element, view) ->
|
|
eventRenderCb(event, element)
|
|
|
|
## fullCalendar time bounds (up & down)
|
|
$scope.calendarConfig.minTime = moment.duration(moment(bookingWindowStart.setting.value).format('HH:mm:ss'))
|
|
$scope.calendarConfig.maxTime = moment.duration(moment(bookingWindowEnd.setting.value).format('HH:mm:ss'))
|
|
|
|
|
|
|
|
##
|
|
# Open a confirmation modal to cancel the booking of a user for the currently selected event.
|
|
# @param slot {Object} reservation slot of a user, inherited from $resource
|
|
##
|
|
$scope.cancelBooking = (slot) ->
|
|
# open a confirmation dialog
|
|
dialogs.confirm
|
|
resolve:
|
|
object: ->
|
|
title: _t('confirmation_required')
|
|
msg: _t("do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION"
|
|
, { GENDER:getGender($scope.currentUser), USER:slot.user.name, DATE:moment(slot.start_at).format('L'), TIME:moment(slot.start_at).format('LT'), RESERVATION:slot.reservable.name }
|
|
, 'messageformat')
|
|
, ->
|
|
# the admin has confirmed, cancel the subscription
|
|
Slot.cancel {id: slot.slot_id}
|
|
, (data, status) -> # success
|
|
# update the canceled_at attribute
|
|
for resa in $scope.reservations
|
|
if resa.slot_id == data.id
|
|
resa.canceled_at = data.canceled_at
|
|
break
|
|
# notify the admin
|
|
growl.success(_t('reservation_was_successfully_cancelled'))
|
|
, (data, status) -> # failed
|
|
growl.error(_t('reservation_cancellation_failed'))
|
|
|
|
|
|
|
|
##
|
|
# Open a confirmation modal to remove a machine for the currently selected availability,
|
|
# except if it is the last machine of the reservation.
|
|
# @param machine {Object} must contain the machine ID and name
|
|
##
|
|
$scope.removeMachine = (machine) ->
|
|
if $scope.availability.machine_ids.length == 1
|
|
growl.error(_t('unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather'))
|
|
else
|
|
# open a confirmation dialog
|
|
dialogs.confirm
|
|
resolve:
|
|
object: ->
|
|
title: _t('confirmation_required')
|
|
msg: _t('do_you_really_want_to_remove_MACHINE_from_this_slot', {GENDER:getGender($scope.currentUser), MACHINE:machine.name}, "messageformat") + ' ' +
|
|
_t('this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing') + ' ' +
|
|
_t('beware_this_cannot_be_reverted')
|
|
, ->
|
|
# the admin has confirmed, remove the machine
|
|
machines = $scope.availability.machine_ids
|
|
for key, m_id in machines
|
|
if m_id == machine.id
|
|
machines.splice(key, 1)
|
|
|
|
Availability.update {id: $scope.availability.id}, {availability: {machines_attributes: [{id: machine.id, _destroy: true}]}}
|
|
, (data, status) -> # success
|
|
# update the machine_ids attribute
|
|
$scope.availability.machine_ids = data.machine_ids
|
|
$scope.availability.title = data.title
|
|
$scope.calendar.fullCalendar 'rerenderEvents'
|
|
# notify the admin
|
|
growl.success(_t('the_machine_was_successfully_removed_from_the_slot'))
|
|
, (data, status) -> # failed
|
|
growl.error(_t('deletion_failed'))
|
|
|
|
|
|
|
|
### PRIVATE SCOPE ###
|
|
|
|
##
|
|
# Return an enumerable meaninful string for the gender of the provider user
|
|
# @param user {Object} Database user record
|
|
# @return {string} 'male' or 'female'
|
|
##
|
|
getGender = (user) ->
|
|
if user.profile
|
|
if user.profile.gender == "true" then 'male' else 'female'
|
|
else 'other'
|
|
|
|
# Triggered when the admin drag on the agenda to create a new reservable slot.
|
|
# @see http://fullcalendar.io/docs/selection/select_callback/
|
|
##
|
|
calendarSelectCb = (start, end, jsEvent, view) ->
|
|
start = moment.tz(start.toISOString(), Fablab.timezone)
|
|
end = moment.tz(end.toISOString(), Fablab.timezone)
|
|
# first we check that the selected slot is an N-hours multiple (ie. not decimal)
|
|
if Number.isInteger(parseInt((end.valueOf() - start.valueOf()) / (SLOT_MULTIPLE * 1000), 10)/SLOT_MULTIPLE)
|
|
today = new Date()
|
|
if (parseInt((start.valueOf() - today) / (60 * 1000), 10) >= 0)
|
|
# then we open a modal window to let the admin specify the slot type
|
|
modalInstance = $uibModal.open
|
|
templateUrl: '<%= asset_path "admin/calendar/eventModal.html" %>'
|
|
controller: 'CreateEventModalController'
|
|
resolve:
|
|
start: -> start
|
|
end: -> end
|
|
# when the modal is closed, we send the slot to the server for saving
|
|
modalInstance.result.then (availability) ->
|
|
$scope.calendar.fullCalendar 'renderEvent',
|
|
id: availability.id
|
|
title: availability.title,
|
|
start: availability.start_at
|
|
end: availability.end_at
|
|
textColor: 'black'
|
|
backgroundColor: availability.backgroundColor
|
|
borderColor: availability.borderColor
|
|
tag_ids: availability.tag_ids
|
|
machine_ids: availability.machine_ids
|
|
, true
|
|
, ->
|
|
$scope.calendar.fullCalendar('unselect')
|
|
|
|
$scope.calendar.fullCalendar('unselect')
|
|
|
|
|
|
|
|
##
|
|
# Triggered when the admin clicks on a availability slot in the agenda.
|
|
# @see http://fullcalendar.io/docs/mouse/eventClick/
|
|
##
|
|
calendarEventClickCb = (event, jsEvent, view) ->
|
|
|
|
$scope.availability = event
|
|
|
|
# if the user has clicked on the delete event button, delete the event
|
|
if ($(jsEvent.target).hasClass('remove-event'))
|
|
Availability.delete id: event.id, ->
|
|
$scope.calendar.fullCalendar 'removeEvents', event.id
|
|
for _event, i in $scope.eventSources[0].events
|
|
if _event.id == event.id
|
|
$scope.eventSources[0].events.splice(i,1)
|
|
|
|
growl.success(_t('the_slot_START-END_has_been_successfully_deleted', {START:+moment(event.start).format('LL LT'), END:moment(event.end).format('LT')}))
|
|
,->
|
|
growl.error(_t('unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member', {START:+moment(event.start).format('LL LT'), END:moment(event.end).format('LT')}))
|
|
# if the user has only clicked on the event, display its reservations
|
|
else
|
|
Availability.reservations {id: event.id}, (reservations) ->
|
|
$scope.reservations = reservations
|
|
|
|
|
|
|
|
|
|
##
|
|
# Triggered when fullCalendar tries to graphicaly render an event block.
|
|
# Append the event tag into the block, just after the event title.
|
|
# @see http://fullcalendar.io/docs/event_rendering/eventRender/
|
|
##
|
|
eventRenderCb = (event, element) ->
|
|
if event.tag_ids.length > 0
|
|
Availability.get {id: event.id}, (avail) ->
|
|
html = ''
|
|
for tag in avail.tags
|
|
html += "<span class='label label-success text-white'>#{tag.name}</span> "
|
|
element.find('.fc-title').append("<br/>"+html)
|
|
|
|
]
|
|
|
|
|
|
|
|
##
|
|
# Controller used in the slot creation modal window
|
|
##
|
|
Application.Controllers.controller 'CreateEventModalController', ["$scope", "$uibModalInstance", "moment", "start", "end", "Machine", "Availability", "Training", 'Tag', 'growl', '_t', ($scope, $uibModalInstance, moment, start, end, Machine, Availability, Training, Tag, growl, _t) ->
|
|
|
|
## $uibModal parameter
|
|
$scope.start = start
|
|
|
|
## $uibModal parameter
|
|
$scope.end = end
|
|
|
|
## machines list
|
|
$scope.machines = []
|
|
|
|
## trainings list
|
|
$scope.trainings = []
|
|
|
|
## machines associated with the created slot
|
|
$scope.selectedMachines = []
|
|
|
|
## the user is not able to edit the ending time of the availability, unless he set the type to 'training'
|
|
$scope.endDateReadOnly = true
|
|
|
|
## timepickers configuration
|
|
$scope.timepickers =
|
|
start:
|
|
hstep: 1
|
|
mstep: 5
|
|
end:
|
|
hstep: 1
|
|
mstep: 5
|
|
|
|
## slot details
|
|
$scope.availability =
|
|
start_at: start
|
|
end_at: end
|
|
available_type: 'machines' # default
|
|
|
|
|
|
|
|
##
|
|
# Adds or removes the provided machine from the current slot
|
|
# @param machine {Object}
|
|
##
|
|
$scope.toggleSelection = (machine)->
|
|
index = $scope.selectedMachines.indexOf(machine)
|
|
if index > -1
|
|
$scope.selectedMachines.splice(index, 1)
|
|
else
|
|
$scope.selectedMachines.push(machine)
|
|
|
|
|
|
|
|
##
|
|
# Callback for the modal window validation: save the slot and closes the modal
|
|
##
|
|
$scope.ok = ->
|
|
if $scope.availability.available_type == "machines"
|
|
if $scope.selectedMachines.length > 0
|
|
$scope.availability.machine_ids = $scope.selectedMachines.map (m) -> m.id
|
|
else
|
|
growl.error(_t('you_should_link_a_training_or_a_machine_to_this_slot'))
|
|
return
|
|
else
|
|
$scope.availability.training_ids = [$scope.selectedTraining.id]
|
|
Availability.save
|
|
availability: $scope.availability
|
|
, (availability) ->
|
|
$uibModalInstance.close(availability)
|
|
|
|
|
|
|
|
##
|
|
# Callback to cancel the slot creation
|
|
##
|
|
$scope.cancel = ->
|
|
$uibModalInstance.dismiss('cancel')
|
|
|
|
|
|
|
|
##
|
|
# Switches the slot type : machine availability or training availability
|
|
##
|
|
$scope.changeAvailableType = ->
|
|
if $scope.availability.available_type == "machines"
|
|
$scope.availability.available_type = "training"
|
|
else
|
|
$scope.availability.available_type = "machines"
|
|
|
|
|
|
|
|
##
|
|
# For training avaiabilities, set the maximum number of people allowed to register on this slot
|
|
##
|
|
$scope.setNbTotalPlaces = ->
|
|
$scope.availability.nb_total_places = $scope.selectedTraining.nb_total_places
|
|
|
|
|
|
### PRIVATE SCOPE ###
|
|
|
|
##
|
|
# Kind of constructor: these actions will be realized first when the controller is loaded
|
|
##
|
|
initialize = ->
|
|
Machine.query().$promise.then (data)->
|
|
$scope.machines = data.map (d) ->
|
|
id: d.id
|
|
name: d.name
|
|
Training.query().$promise.then (data)->
|
|
$scope.trainings = data.map (d) ->
|
|
id: d.id
|
|
name: d.name
|
|
nb_total_places: d.nb_total_places
|
|
if $scope.trainings.length > 0
|
|
$scope.selectedTraining = $scope.trainings[0]
|
|
$scope.setNbTotalPlaces()
|
|
Tag.query().$promise.then (data) ->
|
|
$scope.tags = data
|
|
|
|
## When we configure a machine availability, do not let the user change the end time, as the total
|
|
## time must be dividable by 60 minutes (base slot duration). For training availabilities, the user
|
|
## can configure any duration as it does not matters.
|
|
$scope.$watch 'availability.available_type', (newValue, oldValue, scope) ->
|
|
if newValue == 'machines'
|
|
$scope.endDateReadOnly = true
|
|
diff = moment($scope.end).diff($scope.start, 'hours') # the result is rounded down by moment.js
|
|
$scope.end = moment($scope.start).add(diff, 'hours').toDate()
|
|
$scope.availability.end_at = $scope.end
|
|
else
|
|
$scope.endDateReadOnly = false
|
|
|
|
## When the start date is changed, if we are configuring a machine availability,
|
|
## maintain the relative length of the slot (ie. change the end time accordingly)
|
|
$scope.$watch 'start', (newValue, oldValue, scope) ->
|
|
# for machine availabilities, adjust the end time
|
|
if $scope.availability.available_type == 'machines'
|
|
end = moment($scope.end)
|
|
end.add(moment(newValue).diff(oldValue), 'milliseconds')
|
|
$scope.end = end.toDate()
|
|
else # for training availabilities
|
|
# prevent the admin from setting the begining after the and
|
|
if moment(newValue).add(1, 'hour').isAfter($scope.end)
|
|
$scope.start = oldValue
|
|
# update availability object
|
|
$scope.availability.start_at = $scope.start
|
|
|
|
## Maintain consistency between the end time and the date object in the availability object
|
|
$scope.$watch 'end', (newValue, oldValue, scope) ->
|
|
## we prevent the admin from setting the end of the availability before its begining
|
|
if moment($scope.start).add(1, 'hour').isAfter(newValue)
|
|
$scope.end = oldValue
|
|
# update availability object
|
|
$scope.availability.end_at = $scope.end
|
|
|
|
|
|
|
|
## !!! MUST BE CALLED AT THE END of the controller
|
|
initialize()
|
|
]
|