1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-19 13:54:25 +01:00

display spaces in admin calendar

This commit is contained in:
Sylvain 2017-02-15 13:18:03 +01:00
parent 1855935322
commit d463d4dd5a
8 changed files with 110 additions and 77 deletions

View File

@ -64,8 +64,8 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
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"
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"
, { 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')
, ->
@ -78,9 +78,9 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
resa.canceled_at = data.canceled_at
break
# notify the admin
growl.success(_t('reservation_was_successfully_cancelled'))
growl.success(_t('admin_calendar.reservation_was_successfully_cancelled'))
, (data, status) -> # failed
growl.error(_t('reservation_cancellation_failed'))
growl.error(_t('admin_calendar.reservation_cancellation_failed'))
@ -91,16 +91,16 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
##
$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'))
growl.error(_t('admin_calendar.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')
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')
, ->
# the admin has confirmed, remove the machine
machines = $scope.availability.machine_ids
@ -115,9 +115,9 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
$scope.availability.title = data.title
uiCalendarConfig.calendars.calendar.fullCalendar 'rerenderEvents'
# notify the admin
growl.success(_t('the_machine_was_successfully_removed_from_the_slot'))
growl.success(_t('admin_calendar.the_machine_was_successfully_removed_from_the_slot'))
, (data, status) -> # failed
growl.error(_t('deletion_failed'))
growl.error(_t('admin_calendar.deletion_failed'))
@ -193,9 +193,9 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
Availability.delete id: event.id, ->
uiCalendarConfig.calendars.calendar.fullCalendar 'removeEvents', event.id
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.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')}))
,->
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')}))
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')}))
# if the user has only clicked on the event, display its reservations
else
Availability.reservations {id: event.id}, (reservations) ->
@ -303,14 +303,16 @@ Application.Controllers.controller 'CreateEventModalController', ["$scope", "$ui
# Callback for the modal window validation: save the slot and closes the modal
##
$scope.ok = ->
if $scope.availability.available_type == "machines"
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'))
growl.error(_t('admin_calendar.you_should_select_at_least_a_machine'))
return
else
else if $scope.availability.available_type == 'training'
$scope.availability.training_ids = [$scope.selectedTraining.id]
else if $scope.availability.available_type == 'space'
$scope.availability.space_ids = [$scope.selectedSpace.id]
Availability.save
availability: $scope.availability
, (availability) ->

View File

@ -7,15 +7,15 @@
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1 translate>{{ 'calendar.calendar_management' }}</h1>
<h1 translate>{{ 'admin_calendar.calendar_management' }}</h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
<section class="heading-actions wrapper p-s">
<span class="badge text-sm bg-formation" translate>{{ 'calendar.trainings' }}</span><br>
<span class="badge text-sm bg-machine" translate>{{ 'calendar.machines' }}</span><br>
<span class="badge text-sm bg-space" translate>{{ 'calendar.spaces' }}</span>
<span class="badge text-sm bg-formation" translate>{{ 'admin_calendar.trainings' }}</span><br>
<span class="badge text-sm bg-machine" translate>{{ 'admin_calendar.machines' }}</span><br>
<span class="badge text-sm bg-space" translate>{{ 'admin_calendar.spaces' }}</span>
</section>
</div>
@ -32,7 +32,7 @@
<div class="col-sm-12 col-md-12 col-lg-3">
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3 translate>{{ 'calendar.ongoing_reservations' }}</h3>
<h3 translate>{{ 'admin_calendar.ongoing_reservations' }}</h3>
</div>
<div class="widget-content no-bg auto wrapper">
<ul class="list-unstyled" ng-if="reservations.length > 0">
@ -43,7 +43,7 @@
<span class="btn btn-warning btn-xs" ng-click="cancelBooking(r)" ng-if="!r.canceled_at"><i class="fa fa-times red"></i></span>
</li>
</ul>
<div ng-if="reservations.length == 0" translate>{{ 'calendar.no_reservations' }}</div>
<div ng-if="reservations.length == 0" translate>{{ 'admin_calendar.no_reservations' }}</div>
</div>
</div>
</div>
@ -51,7 +51,7 @@
<div class="col-sm-12 col-md-12 col-lg-3" ng-if="availability.machine_ids.length > 0">
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3 translate>{{ 'calendar.machines' }}</h3>
<h3 translate>{{ 'admin_calendar.machines' }}</h3>
</div>
<div class="widget-content no-bg auto wrapper">
<ul class="list-unstyled">

View File

@ -1,27 +1,27 @@
<div class="modal-header">
<h3 class="text-center red">
{{ 'calendar.DATE_slot' | translate:{DATE:(start | amDateFormat: 'LL')} }} {{start | amDateFormat:'LT'}} - {{end | amDateFormat:'LT'}}
{{ 'admin_calendar.DATE_slot' | translate:{DATE:(start | amDateFormat: 'LL')} }} {{start | amDateFormat:'LT'}} - {{end | amDateFormat:'LT'}}
</h3>
</div>
<div class="modal-body" ng-show="step === 1">
<label class="m-t-sm" translate>{{ 'calendar.what_kind_of_slot_do_you_want_to_create' }}</label>
<label class="m-t-sm" translate>{{ 'admin_calendar.what_kind_of_slot_do_you_want_to_create' }}</label>
<div class="form-group">
<div class="radio">
<label>
<input type="radio" id="training" name="available_type" value="training" ng-model="availability.available_type">
<span translate>{{ 'calendar.training' }}</span>
<span translate>{{ 'admin_calendar.training' }}</span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" id="machine" name="available_type" value="machines" ng-model="availability.available_type">
<span translate>{{ 'calendar.machine' }}</span>
<span translate>{{ 'admin_calendar.machine' }}</span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" id="space" name="available_type" value="space" ng-model="availability.available_type">
<span translate>{{ 'calendar.space' }}</span>
<span translate>{{ 'admin_calendar.space' }}</span>
</label>
</div>
</div>
@ -29,7 +29,7 @@
<div class="modal-body" ng-show="step === 2">
<div ng-show="availability.available_type == 'machines'">
<p class="text-center font-sbold m-t-sm">{{ 'calendar.select_some_machines' | translate }}</p>
<p class="text-center font-sbold m-t-sm">{{ 'admin_calendar.select_some_machines' | translate }}</p>
<div class="form-group m-l-xl">
<label class="checkbox" ng-repeat="machine in machines">
@ -43,7 +43,7 @@
</select>
<div class="row m-t">
<div class="form-group">
<label class="col-sm-6 control-label" for="nb_places_training" translate>{{ 'calendar.number_of_tickets' }}</label>
<label class="col-sm-6 control-label" for="nb_places_training" translate>{{ 'admin_calendar.number_of_tickets' }}</label>
<div class="col-sm-6">
<input type="number" id="nb_places_training" class="form-control" ng-model="availability.nb_total_places">
</div>
@ -56,7 +56,7 @@
</select>
<div class="row m-t">
<div class="form-group">
<label class="col-sm-6 control-label" for="nb_places_space" translate>{{ 'calendar.number_of_tickets' }}</label>
<label class="col-sm-6 control-label" for="nb_places_space" translate>{{ 'admin_calendar.number_of_tickets' }}</label>
<div class="col-sm-6">
<input type="number" id="nb_places_space" class="form-control" ng-model="availability.nb_total_places">
</div>
@ -66,19 +66,19 @@
</div>
<div class="modal-body" ng-show="step === 3">
<div id="timeAdjust" class="m-t-sm">
<p class="text-center font-sbold" translate>{{ 'calendar.adjust_the_opening_hours' }}</p>
<p class="text-center font-sbold" translate>{{ 'admin_calendar.adjust_the_opening_hours' }}</p>
<div class="row">
<div class="col-md-3 col-md-offset-2">
<uib-timepicker ng-model="start" hour-step="timepickers.start.hstep" readonly-input="true" minute-step="timepickers.start.mstep" show-meridian="false"></uib-timepicker>
</div>
<span class="col-md-1 m-t-xl m-l" translate>{{ 'calendar.to_time' }}</span>
<span class="col-md-1 m-t-xl m-l" translate>{{ 'admin_calendar.to_time' }}</span>
<fieldset ng-disabled="endDateReadOnly" class="col-md-5">
<uib-timepicker ng-model="end" hour-step="timepickers.end.hstep" readonly-input="true" minute-step="timepickers.end.mstep" show-meridian="false"></uib-timepicker>
</fieldset>
</div>
</div>
<div id="tagAssociate" class="m-t-lg">
<p class="text-center font-sbold" translate>{{ 'calendar.restrict_this_slot_with_labels_(optional)' }}</p>
<p class="text-center font-sbold" translate>{{ 'admin_calendar.restrict_this_slot_with_labels_(optional)' }}</p>
<div class="row">
<div class="col-sm-12">
<ui-select multiple ng-model="availability.tag_ids" class="form-control">
@ -94,12 +94,12 @@
</div>
</div>
<div class="modal-footer" ng-show="step < 3">
<button class="btn btn-info" ng-click="previous()" ng-disabled="step === 1" translate>{{ 'calendar.previous' }}</button>
<button class="btn btn-info" ng-click="next()" translate>{{ 'calendar.next' }}</button>
<button class="btn btn-info" ng-click="previous()" ng-disabled="step === 1" translate>{{ 'admin_calendar.previous' }}</button>
<button class="btn btn-info" ng-click="next()" translate>{{ 'admin_calendar.next' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
</div>
<div class="modal-footer" ng-show="step === 3">
<button class="btn btn-info" ng-click="previous()" translate>{{ 'calendar.previous' }}</button>
<button class="btn btn-info" ng-click="previous()" translate>{{ 'admin_calendar.previous' }}</button>
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
</div>

View File

@ -10,7 +10,7 @@ class API::AvailabilitiesController < API::ApiController
authorize Availability
start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start])
end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day
@availabilities = Availability.includes(:machines,:tags,:trainings).where.not(available_type: 'event')
@availabilities = Availability.includes(:machines, :tags, :trainings, :spaces).where.not(available_type: 'event')
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
end
@ -161,7 +161,7 @@ class API::AvailabilitiesController < API::ApiController
end
def availability_params
params.require(:availability).permit(:start_at, :end_at, :available_type, :machine_ids, :training_ids, :nb_total_places, machine_ids: [], training_ids: [], tag_ids: [],
params.require(:availability).permit(:start_at, :end_at, :available_type, :machine_ids, :training_ids, :nb_total_places, machine_ids: [], training_ids: [], space_ids: [], tag_ids: [],
:machines_attributes => [:id, :_destroy])
end

View File

@ -1,18 +1,22 @@
module AvailabilityHelper
MACHINE_COLOR = '#e4cd78'
TRAINING_COLOR = '#bd7ae9'
SPACE_COLOR = '#3fc7ff'
EVENT_COLOR = '#dd7e6b'
IS_RESERVED_BY_CURRENT_USER = '#b2e774'
MACHINE_IS_RESERVED_BY_USER = '#1d98ec'
IS_COMPLETED = '#eeeeee'
def availability_border_color(availability)
if availability.available_type == 'machines'
MACHINE_COLOR
elsif availability.available_type == 'training'
TRAINING_COLOR
else
EVENT_COLOR
case availability.available_type
when 'machines'
MACHINE_COLOR
when 'training'
TRAINING_COLOR
when 'space'
SPACE_COLOR
else
EVENT_COLOR
end
end

View File

@ -12,6 +12,9 @@ class Availability < ActiveRecord::Base
has_many :trainings_availabilities, dependent: :destroy
has_many :trainings, through: :trainings_availabilities
has_many :spaces_availabilities, dependent: :destroy
has_many :spaces, through: :spaces_availabilities
has_many :slots
has_many :reservations, through: :slots
@ -23,6 +26,7 @@ class Availability < ActiveRecord::Base
scope :machines, -> { where(available_type: 'machines') }
scope :trainings, -> { includes(:trainings).where(available_type: 'training') }
scope :spaces, -> { includes(:spaces).where(available_type: 'space') }
attr_accessor :is_reserved, :slot_id, :can_modify
@ -43,10 +47,18 @@ class Availability < ActiveRecord::Base
end
def safe_destroy
if available_type == 'machines'
reservations = Reservation.where(reservable_type: 'Machine', reservable_id: machine_ids).joins(:slots).where('slots.availability_id = ?', id)
else
reservations = Reservation.where(reservable_type: 'Training', reservable_id: training_ids).joins(:slots).where('slots.availability_id = ?', id)
case available_type
when 'machines'
reservations = Reservation.where(reservable_type: 'Machine', reservable_id: machine_ids).joins(:slots).where('slots.availability_id = ?', id)
when 'training'
reservations = Reservation.where(reservable_type: 'Training', reservable_id: training_ids).joins(:slots).where('slots.availability_id = ?', id)
when 'space'
reservations = Reservation.where(reservable_type: 'Space', reservable_id: space_ids).joins(:slots).where('slots.availability_id = ?', id)
when 'event'
reservations = Reservation.where(reservable_type: 'Event', reservable_id: event&.id).joins(:slots).where('slots.availability_id = ?', id)
else
STDERR.puts "[safe_destroy] Availability with unknown type #{available_type}"
reservations = []
end
if reservations.size == 0
# this update may not call any rails callbacks, that's why we use direct SQL update
@ -58,15 +70,21 @@ class Availability < ActiveRecord::Base
end
def title(filter = {})
if available_type == 'machines'
if filter[:machine_ids]
return machines.to_ary.delete_if {|m| !filter[:machine_ids].include?(m.id)}.map(&:name).join(' - ')
end
return machines.map(&:name).join(' - ')
elsif available_type == 'event'
event.name
else
trainings.map(&:name).join(' - ')
case available_type
when 'machines'
if filter[:machine_ids]
return machines.to_ary.delete_if {|m| !filter[:machine_ids].include?(m.id)}.map(&:name).join(' - ')
end
return machines.map(&:name).join(' - ')
when 'event'
event.name
when 'training'
trainings.map(&:name).join(' - ')
when 'space'
spaces.map(&:name).join(' - ')
else
STDERR.puts "[title] Availability with unknown type #{available_type}"
'???'
end
end
@ -74,23 +92,23 @@ class Availability < ActiveRecord::Base
# if haven't defined a nb_total_places, places are unlimited
def is_completed
return false if nb_total_places.blank?
if available_type == 'training'
if available_type == 'training' || available_type == 'space'
nb_total_places <= slots.to_a.select {|s| s.canceled_at == nil }.size
elsif available_type == 'event'
event.nb_free_places == 0
end
end
# TODO: refactoring this function for avoid N+1 query
def nb_total_places
if available_type == 'training'
if read_attribute(:nb_total_places).present?
read_attribute(:nb_total_places)
case available_type
when 'training'
super.presence || trainings.map {|t| t.nb_total_places}.reduce(:+)
when 'event'
event.nb_total_places
when 'space'
super.presence || spaces.map {|s| s.default_places}.reduce(:+)
else
trainings.first.nb_total_places unless trainings.empty?
end
elsif available_type == 'event'
event.nb_total_places
nil
end
end
@ -98,12 +116,17 @@ class Availability < ActiveRecord::Base
def as_indexed_json
json = JSON.parse(to_json)
json['hours_duration'] = (end_at - start_at) / (60 * 60)
if available_type == 'machines'
json['subType'] = machines_availabilities.map{|ma| ma.machine.friendly_id}
elsif available_type == 'training'
json['subType'] = trainings_availabilities.map{|ta| ta.training.friendly_id}
elsif available_type == 'event'
json['subType'] = [event.category.friendly_id]
case available_type
when 'machines'
json['subType'] = machines_availabilities.map{|ma| ma.machine.friendly_id}
when'training'
json['subType'] = trainings_availabilities.map{|ta| ta.training.friendly_id}
when 'event'
json['subType'] = [event.category.friendly_id]
when 'space'
json['subType'] = spaces_availabilities.map{|sa| sa.space.friendly_id}
else
json['subType'] = []
end
json['bookable_hours'] = json['hours_duration'] * json['subType'].length
json['date'] = start_at.to_date

View File

@ -13,14 +13,16 @@ en:
calendar:
# manage the trainings & machines slots
calendar:
admin_calendar:
calendar_management: "Calendar management"
trainings: "Trainings"
machines: "Machines"
spaces: "Spaces"
ongoing_reservations: "Ongoing reservations"
no_reservations: "No reservations"
confirmation_required: "Confirmation required"
do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION: "Do you really want to cancel the {USER}'s reservation, the {DATE} at {TIME}, concerning {RESERVATION}?" # messageFormat interpolation
reservation_was_successfully_cancelled: "Reservation was successfully cancelled."
reservation_cancellation_failed: "Reservation cancellation failed."
unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather: "Unable to remove the last machine of the slot. Delete the slot rather."
do_you_really_want_to_remove_MACHINE_from_this_slot: "Do you really want to remove \"{MACHINE}\" from this slot?" # messageFormat interpolation
@ -42,7 +44,7 @@ en:
restrict_this_slot_with_labels_(optional): "Restrict this slot with labels (optional)"
the_slot_START-END_has_been_successfully_deleted: "The slot {{START}} - {{END}} has been successfully deleted" # angular interpolation
unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Unable to delete the slot {{START}} - {{END}} because it's already reserved by a member" # angular interpolation
you_should_link_a_training_or_a_machine_to_this_slot: "You should link a training or a machine to this slot."
you_should_select_at_least_a_machine: "You should select at least one machine on this slot."
project_elements:
# management of the projects' components

View File

@ -13,14 +13,16 @@ fr:
calendar:
# gestion des créneaux machines et formations
calendar:
admin_calendar:
calendar_management: "Gestion du calendrier"
trainings: "Formations"
machines: "Machines"
spaces: "Espaces"
ongoing_reservations: "Réservations en cours"
no_reservations: "Aucune réservation"
confirmation_required: "Confirmation requise"
do_you_really_want_to_cancel_the_USER_s_reservation_the_DATE_at_TIME_concerning_RESERVATION: "Êtes-vous {GENDER, select, female{sûre} other{sûr}} de vouloir annuler la réservation de {USER}, le {DATE} à {TIME}, concernant {RESERVATION} ?" # messageFormat interpolation
reservation_was_successfully_cancelled: "La réservation a bien été annulée."
reservation_cancellation_failed: "L'annulation de la réservation a échouée."
unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather: "Impossible de supprimer la dernière machine du créneau. Supprimez plutôt le créneau."
do_you_really_want_to_remove_MACHINE_from_this_slot: "Êtes-vous {GENDER, select, female{sûre} other{sûr}} de vouloir retirer \"{MACHINE}\" de ce créneau ?" # messageFormat interpolation
@ -42,7 +44,7 @@ fr:
restrict_this_slot_with_labels_(optional): "Restreindre ce créneau avec des étiquettes (optionnel)"
the_slot_START-END_has_been_successfully_deleted: "Le créneau {{START}} - {{END}} a bien été supprimé" # angular interpolation
unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Le créneau {{START}} - {{END}} n'a pu être supprimé car il est déjà réservé par un membre" # angular interpolation
you_should_link_a_training_or_a_machine_to_this_slot: "Vous devriez associer une formation ou une machine à ce créneau."
you_should_select_at_least_a_machine: "Vous devriez sélectionne au moins une machine pour ce créneau."
project_elements:
# gestion des éléments constituant les projets