1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-11-30 11:24:21 +01:00
fab-manager/app/assets/javascripts/controllers/admin/calendar.coffee.erb

475 lines
17 KiB
Plaintext
Raw Normal View History

2016-03-23 18:39:41 +01:00
'use strict'
##
# Controller used in the calendar management page
##
2017-03-02 12:34:28 +01:00
Application.Controllers.controller "AdminCalendarController", ["$scope", "$state", "$uibModal", "moment", "Availability", 'Slot', 'Setting', 'Export', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', '_t', 'uiCalendarConfig', 'CalendarConfig'
($scope, $state, $uibModal, moment, Availability, Slot, Setting, Export, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, _t, uiCalendarConfig, CalendarConfig) ->
2016-03-23 18:39:41 +01:00
### 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'
# 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
url: '/api/availabilities'
2016-03-23 18:39:41 +01:00
textColor: 'black'
## fullCalendar (v2) configuration
$scope.calendarConfig = CalendarConfig
2016-03-23 18:39:41 +01:00
slotDuration: BASE_SLOT
snapDuration: BOOKING_SNAP
selectable: true
selecHelper: true
minTime: moment.duration(moment(bookingWindowStart.setting.value).format('HH:mm:ss'))
maxTime: moment.duration(moment(bookingWindowEnd.setting.value).format('HH:mm:ss'))
2016-03-23 18:39:41 +01:00
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)
loading: (isLoading, view ) ->
loadingCb(isLoading, view)
2016-03-23 18:39:41 +01:00
##
# 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: ->
2017-02-15 13:18:03 +01:00
title: _t('admin_calendar.confirmation_required')
msg: _t("admin_calendar.do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION"
2016-03-23 18:39:41 +01:00
, { 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
2017-02-15 13:18:03 +01:00
growl.success(_t('admin_calendar.reservation_was_successfully_cancelled'))
2016-03-23 18:39:41 +01:00
, (data, status) -> # failed
2017-02-15 13:18:03 +01:00
growl.error(_t('admin_calendar.reservation_cancellation_failed'))
2016-03-23 18:39:41 +01:00
##
# 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
2017-02-15 13:18:03 +01:00
growl.error(_t('admin_calendar.unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather'))
2016-03-23 18:39:41 +01:00
else
# open a confirmation dialog
dialogs.confirm
resolve:
object: ->
2017-02-15 13:18:03 +01:00
title: _t('admin_calendar.confirmation_required')
msg: _t('admin_calendar.do_you_really_want_to_remove_MACHINE_from_this_slot', {GENDER:getGender($scope.currentUser), MACHINE:machine.name}, "messageformat") + ' ' +
_t('admin_calendar.this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing') + ' ' +
_t('admin_calendar.beware_this_cannot_be_reverted')
2016-03-23 18:39:41 +01:00
, ->
# 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
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
2016-03-23 18:39:41 +01:00
# notify the admin
2017-02-15 13:18:03 +01:00
growl.success(_t('admin_calendar.the_machine_was_successfully_removed_from_the_slot'))
2016-03-23 18:39:41 +01:00
, (data, status) -> # failed
2017-02-15 13:18:03 +01:00
growl.error(_t('admin_calendar.deletion_failed'))
2016-03-23 18:39:41 +01:00
2017-03-02 12:34:28 +01:00
##
# Callback to alert the admin that the export request was acknowledged and is
# processing right now.
##
$scope.alertExport = (type) ->
Export.status({category: 'availabilities', type: type}).then (res) ->
unless (res.data.exists)
growl.success _t('admin_calendar.export_is_running_you_ll_be_notified_when_its_ready')
##
# Mark the selected slot as unavailable for new reservations or allow reservations again on it
##
$scope.toggleLockReservations = ->
# first, define a shortcut to the lock property
locked = $scope.availability.lock
# then check if we'll allow reservations locking
prevent = !locked # if currently locked, allow unlock anyway
if (!locked)
prevent = false
angular.forEach $scope.reservations, (r) ->
if r.canceled_at == null
prevent = true # if currently unlocked and has any non-cancelled reservation, disallow locking
if (!prevent)
# open a confirmation dialog
dialogs.confirm
resolve:
object: ->
title: _t('admin_calendar.confirmation_required')
2017-09-07 10:47:18 +02:00
msg: if locked then _t("admin_calendar.do_you_really_want_to_allow_reservations") else _t("admin_calendar.do_you_really_want_to_block_this_slot")
, ->
# the admin has confirmed, lock/unlock the slot
Availability.lock {id: $scope.availability.id}, {lock: !locked}
, (data) -> # success
$scope.availability = data
growl.success(if locked then _t('admin_calendar.unlocking_success') else _t('admin_calendar.locking_success') )
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
, (error) -> # failed
growl.error(if locked then _t('admin_calendar.unlocking_failed') else _t('admin_calendar.locking_failed'))
else
growl.error(_t('admin_calendar.unlockable_because_reservations'))
##
# Confirm and destroy the slot in $scope.availability
##
$scope.removeSlot = ->
# open a confirmation dialog
dialogs.confirm
resolve:
object: ->
title: _t('admin_calendar.confirmation_required')
msg: _t("admin_calendar.do_you_really_want_to_delete_this_slot")
, ->
# the admin has confirmed, delete the slot
Availability.delete id: $scope.availability.id, ->
uiCalendarConfig.calendars.calendar.fullCalendar 'removeEvents', $scope.availability.id
growl.success(_t('admin_calendar.the_slot_START-END_has_been_successfully_deleted', {START:moment(event.start).format('LL LT'), END:moment(event.end).format('LT')}))
$scope.availability = null
,->
growl.error(_t('admin_calendar.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')}))
### PRIVATE SCOPE ###
2016-03-23 18:39:41 +01:00
##
# 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
machinesPromise: ['Machine', (Machine)->
Machine.query().$promise
]
trainingsPromise: ['Training', (Training)->
Training.query().$promise
]
spacesPromise: ['Space', (Space)->
Space.query().$promise
]
2016-03-23 18:39:41 +01:00
# when the modal is closed, we send the slot to the server for saving
modalInstance.result.then (availability) ->
uiCalendarConfig.calendars.calendar.fullCalendar 'renderEvent',
2016-03-23 18:39:41 +01:00
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
tags: availability.tags
2016-03-23 18:39:41 +01:00
machine_ids: availability.machine_ids
, true
, ->
uiCalendarConfig.calendars.calendar.fullCalendar('unselect')
2016-03-23 18:39:41 +01:00
uiCalendarConfig.calendars.calendar.fullCalendar('unselect')
2016-03-23 18:39:41 +01:00
##
# 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'))
$scope.removeSlot()
2016-03-23 18:39:41 +01:00
# 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) ->
element.find('.fc-content').prepend('<span class="remove-event">x&nbsp;</span>')
if event.tags.length > 0
html = ''
for tag in event.tags
html += "<span class='label label-success text-white'>#{tag.name}</span> "
element.find('.fc-title').append("<br/>"+html)
# force return to prevent coffee-script auto-return to return random value (possiblity falsy)
return
2016-03-23 18:39:41 +01:00
##
# Triggered when resource fetching starts/stops.
# @see https://fullcalendar.io/docs/resource_data/loading/
##
loadingCb = (isLoading, view) ->
if (isLoading)
# we remove existing events when fetching starts to prevent duplicates
uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents')
2016-03-23 18:39:41 +01:00
]
##
# Controller used in the slot creation modal window
##
Application.Controllers.controller 'CreateEventModalController', ["$scope", "$uibModalInstance", "moment", "start", "end", "machinesPromise", "Availability", "trainingsPromise", "spacesPromise", 'Tag', 'growl', '_t'
, ($scope, $uibModalInstance, moment, start, end, machinesPromise, Availability, trainingsPromise, spacesPromise, Tag, growl, _t) ->
2016-03-23 18:39:41 +01:00
## $uibModal parameter
$scope.start = start
## $uibModal parameter
$scope.end = end
## machines list
$scope.machines = machinesPromise
2016-03-23 18:39:41 +01:00
## trainings list
$scope.trainings = trainingsPromise
## spaces list
$scope.spaces = spacesPromise
2016-03-23 18:39:41 +01:00
## machines associated with the created slot
$scope.selectedMachines = []
## training associated with the created slot
$scope.selectedTraining = null
## space associated with the created slot
$scope.selectedSpace = null
## UI step
$scope.step = 1
2016-03-23 18:39:41 +01:00
## 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 = ->
2017-02-15 13:18:03 +01:00
if $scope.availability.available_type == 'machines'
2016-03-23 18:39:41 +01:00
if $scope.selectedMachines.length > 0
$scope.availability.machine_ids = $scope.selectedMachines.map (m) -> m.id
else
2017-02-15 13:18:03 +01:00
growl.error(_t('admin_calendar.you_should_select_at_least_a_machine'))
2016-03-23 18:39:41 +01:00
return
2017-02-15 13:18:03 +01:00
else if $scope.availability.available_type == 'training'
2016-03-23 18:39:41 +01:00
$scope.availability.training_ids = [$scope.selectedTraining.id]
2017-02-15 13:18:03 +01:00
else if $scope.availability.available_type == 'space'
$scope.availability.space_ids = [$scope.selectedSpace.id]
2016-03-23 18:39:41 +01:00
Availability.save
availability: $scope.availability
, (availability) ->
$uibModalInstance.close(availability)
##
# Move the modal UI to the next step
2016-03-23 18:39:41 +01:00
##
$scope.next = ->
$scope.setNbTotalPlaces() if $scope.step == 1
$scope.step++
2016-03-23 18:39:41 +01:00
##
# Move the modal UI to the next step
2016-03-23 18:39:41 +01:00
##
$scope.previous = ->
$scope.step--
##
# Callback to cancel the slot creation
##
$scope.cancel = ->
$uibModalInstance.dismiss('cancel')
2016-03-23 18:39:41 +01:00
##
# For training avaiabilities, set the maximum number of people allowed to register on this slot
##
$scope.setNbTotalPlaces = ->
if $scope.availability.available_type == 'training'
$scope.availability.nb_total_places = $scope.selectedTraining.nb_total_places
else if $scope.availability.available_type == 'space'
$scope.availability.nb_total_places = $scope.selectedSpace.default_places
2016-03-23 18:39:41 +01:00
### PRIVATE SCOPE ###
##
# Kind of constructor: these actions will be realized first when the controller is loaded
##
initialize = ->
if $scope.trainings.length > 0
$scope.selectedTraining = $scope.trainings[0]
if $scope.spaces.length > 0
$scope.selectedSpace = $scope.spaces[0]
2016-03-23 18:39:41 +01:00
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' or newValue == 'space'
2016-03-23 18:39:41 +01:00
$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 or space availabilities, adjust the end time
if $scope.availability.available_type == 'machines' or $scope.availability.available_type == 'space'
2016-03-23 18:39:41 +01:00
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()
]