From 5a0185dd48b5c9e3f3e6ac43e5153321f10539d7 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 30 Sep 2020 16:32:01 +0200 Subject: [PATCH] fix machine reservation calendar --- .../javascript/controllers/machines.js.erb | 78 +++++++------ .../src/javascript/directives/cart.js | 108 +++++++++--------- .../api/availabilities/machine.json.jbuilder | 2 +- 3 files changed, 100 insertions(+), 88 deletions(-) diff --git a/app/frontend/src/javascript/controllers/machines.js.erb b/app/frontend/src/javascript/controllers/machines.js.erb index 9fa7794dd..a53138be2 100644 --- a/app/frontend/src/javascript/controllers/machines.js.erb +++ b/app/frontend/src/javascript/controllers/machines.js.erb @@ -495,7 +495,7 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat $scope.markSlotAsAdded = function () { $scope.selectedEvent.backgroundColor = FREE_SLOT_BORDER_COLOR; $scope.selectedEvent.title = _t('app.logged.machines_reserve.i_reserve'); - return updateCalendar(); + updateEvents($scope.selectedEvent); }; /** @@ -506,11 +506,11 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat slot.borderColor = FREE_SLOT_BORDER_COLOR; slot.title = ''; slot.isValid = false; - slot.id = null; + slot.slot_id = null; slot.is_reserved = false; slot.can_modify = false; slot.offered = false; - return updateCalendar(); + updateEvents(slot); }; /** @@ -524,7 +524,7 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat $scope.markSlotAsModifying = function () { $scope.selectedEvent.backgroundColor = '#eee'; $scope.selectedEvent.title = _t('app.logged.machines_reserve.i_change'); - return updateCalendar(); + updateEvents($scope.selectedEvent); }; /** @@ -534,34 +534,44 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat if ($scope.events.placable) { $scope.events.placable.backgroundColor = 'white'; $scope.events.placable.title = ''; + updateEvents($scope.events.placable); } if (!$scope.events.placable || ($scope.events.placable._id !== $scope.selectedEvent._id)) { $scope.selectedEvent.backgroundColor = '#bbb'; $scope.selectedEvent.title = _t('app.logged.machines_reserve.i_shift'); + updateEvents($scope.selectedEvent); } - return updateCalendar(); }; /** * When modifying an already booked reservation, callback when the modification was successfully done. */ $scope.modifyMachineSlot = function () { - $scope.events.placable.title = $scope.currentUser.id === $scope.events.modifiable.user.id ? _t('app.logged.machines_reserve.i_ve_reserved') : _t('app.logged.machines_reserve.not_available'); - $scope.events.placable.backgroundColor = 'white'; - $scope.events.placable.borderColor = $scope.events.modifiable.borderColor; - $scope.events.placable.id = $scope.events.modifiable.id; - $scope.events.placable.is_reserved = true; - $scope.events.placable.can_modify = true; - $scope.events.placable.user = angular.copy($scope.events.modifiable.user); + const save = { + slotId: $scope.events.modifiable.slot_id, + borderColor: $scope.events.modifiable.borderColor, + user: angular.copy($scope.events.modifiable.user), + title: $scope.currentUser.id === $scope.events.modifiable.user.id ? _t('app.logged.machines_reserve.i_ve_reserved') : _t('app.logged.machines_reserve.not_available') + }; $scope.events.modifiable.backgroundColor = 'white'; $scope.events.modifiable.title = ''; $scope.events.modifiable.borderColor = FREE_SLOT_BORDER_COLOR; - $scope.events.modifiable.id = null; + $scope.events.modifiable.slot_id = null; $scope.events.modifiable.is_reserved = false; $scope.events.modifiable.can_modify = false; + updateEvents($scope.events.modifiable); - return updateCalendar(); + $scope.events.placable.title = save.title; + $scope.events.placable.backgroundColor = 'white'; + $scope.events.placable.borderColor = save.borderColor; + $scope.events.placable.slot_id = save.slotId; + $scope.events.placable.is_reserved = true; + $scope.events.placable.can_modify = true; + $scope.events.placable.user = angular.copy(save.user); + updateEvents($scope.events.placable); + + refetchCalendar(); }; /** @@ -575,7 +585,7 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat $scope.events.modifiable.title = $scope.currentUser.id === $scope.events.modifiable.user.id ? _t('app.logged.machines_reserve.i_ve_reserved') : _t('app.logged.machines_reserve.not_available'); $scope.events.modifiable.backgroundColor = 'white'; - return updateCalendar(); + updateEvents($scope.events.placable, $scope.events.modifiable); }; /** @@ -648,7 +658,7 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat $scope.selectedPlan = null; } - return refetchCalendar(); + refetchCalendar(); }; /** @@ -662,11 +672,13 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat * Kind of constructor: these actions will be realized first when the controller is loaded */ const initialize = function () { - Availability.machine({ machineId: $stateParams.id }, function (availabilities) { - $scope.eventSources.push({ - events: availabilities, - textColor: 'black' - }); + $scope.eventSources.push({ + events: function (start, end, timezone, callback) { + Availability.machine({ machineId: $stateParams.id }, function (availabilities) { + callback(availabilities); + }); + }, + textColor: 'black' }); if ($scope.currentUser.role !== 'admin') { @@ -680,7 +692,7 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat * the user's subscription (current or about to be took) and the time (the user cannot modify a booked reservation * if it's too late). */ - var calendarEventClickCb = function (event, jsEvent, view) { + const calendarEventClickCb = function (event, jsEvent, view) { $scope.selectedEvent = event; return $scope.selectionTime = new Date(); }; @@ -690,7 +702,7 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat * Append the event tag into the block, just after the event title. * @see http://fullcalendar.io/docs/event_rendering/eventRender/ */ - var eventRenderCb = function (event, element) { + const eventRenderCb = function (event, element) { if (($scope.currentUser.role === 'admin') && (event.tags.length > 0)) { let html = ''; for (let tag of Array.from(event.tags)) { @@ -708,28 +720,30 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat * @param reservation {Object} * @param user {Object} user associated with the slot */ - var updateMachineSlot = function (slot, reservation, user) { + const updateMachineSlot = function (slot, reservation, user) { angular.forEach(reservation.slots, function (s) { if (slot.start.isSame(s.start_at)) { - slot.id = s.id; - return slot.user = user; + slot.slot_id = s.id; + slot.user = user; } }); + updateEvents(slot); }; /** * Update the calendar's display to render the new attributes of the events + * @param events Object[] events to update in full-calendar */ - var updateCalendar = function () { uiCalendarConfig.calendars.calendar.fullCalendar('rerenderEvents'); }; + const updateEvents = function (...events) { + const realEvents = events.filter(e => !_.isNil(e)); + uiCalendarConfig.calendars.calendar.fullCalendar('updateEvents', realEvents); + }; /** * Asynchronously fetch the events from the API and refresh the calendar's view with these new events */ - var refetchCalendar = function () { - $timeout(function () { - uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents'); - return uiCalendarConfig.calendars.calendar.fullCalendar('rerenderEvents'); - }); + const refetchCalendar = function () { + uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents'); }; // !!! MUST BE CALLED AT THE END of the controller diff --git a/app/frontend/src/javascript/directives/cart.js b/app/frontend/src/javascript/directives/cart.js index 467ab2222..969f5ce07 100644 --- a/app/frontend/src/javascript/directives/cart.js +++ b/app/frontend/src/javascript/directives/cart.js @@ -10,7 +10,7 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', 'growl', 'Auth', 'Price', 'Wallet', 'CustomAsset', 'Slot', 'AuthService', 'helpers', '_t', +Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs', 'growl', 'Auth', 'Price', 'Wallet', 'CustomAsset', 'Slot', 'AuthService', 'helpers', '_t', function ($rootScope, $uibModal, dialogs, growl, Auth, Price, Wallet, CustomAsset, Slot, AuthService, helpers, _t) { return ({ restrict: 'E', @@ -78,8 +78,8 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', validateSameTimeReservations(slot, function () { slot.isValid = true; updateCartPrice(); - }) - }) + }); + }); }; /** @@ -121,15 +121,15 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', // first, we ensure that a user was selected (admin/manager) or logged (member) const isSelectedUser = Object.keys($scope.user).length > 0; // all slots are in future - const areFutureSlots = _.every($scope.events.reserved, function(s) { + const areFutureSlots = _.every($scope.events.reserved, function (s) { return s.start.isAfter(); }); if (isSelectedUser && areFutureSlots) { return $scope.modePlans = true; - } else if (!isSelectedUser){ + } else if (!isSelectedUser) { // otherwise we alert, this error musn't occur when the current user hasn't the admin role return growl.error(_t('app.shared.cart.please_select_a_member_first')); - } else if (!areFutureSlots){ + } else if (!areFutureSlots) { return growl.error(_t('app.shared.cart.unable_to_select_plan_if_slots_in_the_past')); } }; @@ -140,7 +140,6 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', $scope.payCart = function () { // first, we check that a user was selected if (Object.keys($scope.user).length > 0) { - // check selected user has a subscription, if any slot is restricted for subscriptions const slotValidations = []; let slotNotValid; @@ -148,16 +147,16 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', $scope.events.reserved.forEach(function (slot) { if (slot.plan_ids.length > 0) { if ( - ($scope.selectedPlan && _.include(slot.plan_ids, $scope.selectedPlan.id)) || - ($scope.user.subscribed_plan && _.include(slot.plan_ids, $scope.user.subscribed_plan.id)) + ($scope.selectedPlan && _.includes(slot.plan_ids, $scope.selectedPlan.id)) || + ($scope.user.subscribed_plan && _.includes(slot.plan_ids, $scope.user.subscribed_plan.id)) ) { slotValidations.push(true); } else { slotNotValid = slot; - if ($scope.selectedPlan && !_.include(slot.plan_ids, $scope.selectedPlan.id)) { + if ($scope.selectedPlan && !_.includes(slot.plan_ids, $scope.selectedPlan.id)) { slotNotValidError = 'selectedPlanError'; } - if ($scope.user.subscribed_plan && !_.include(slot.plan_ids, $scope.user.subscribed_plan.id)) { + if ($scope.user.subscribed_plan && !_.includes(slot.plan_ids, $scope.user.subscribed_plan.id)) { slotNotValidError = 'userPlanError'; } if (!$scope.selectedPlan || !$scope.user.subscribed_plan) { @@ -178,11 +177,11 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', size: 'md', controller: 'ReserveSlotWithoutPlanController', resolve: { - slot: function() { return slotNotValid; }, - slotNotValidError: function() { return slotNotValidError; }, + slot: function () { return slotNotValid; }, + slotNotValidError: function () { return slotNotValidError; } } }); - modalInstance.result.then(function(res) { + modalInstance.result.then(function (res) { return paySlots(); }); } @@ -199,7 +198,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', * When modifying an already booked reservation, confirm the modification. */ $scope.modifySlot = function () { - Slot.update({ id: $scope.events.modifiable.id }, { + Slot.update({ id: $scope.events.modifiable.slot_id }, { slot: { start_at: $scope.events.placable.start, end_at: $scope.events.placable.end, @@ -216,23 +215,23 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', }; // -> reset the 'moving' status $scope.events.placable = null; - return $scope.events.modifiable = null; + $scope.events.modifiable = null; } , function (err) { // failure growl.error(_t('app.shared.cart.unable_to_change_the_reservation')); - return console.error(err); + console.error(err); }); }; /** * Cancel the current booking modification, reseting the whole process - * @param event {Object} see https://docs.angularjs.org/guide/expression#-event- + * @param [event] {Object} see https://docs.angularjs.org/guide/expression#-event- */ $scope.cancelModifySlot = function (event) { if (event) { event.preventDefault(); } if (typeof $scope.onSlotModifyCancel === 'function') { $scope.onSlotModifyCancel(); } $scope.events.placable = null; - return $scope.events.modifiable = null; + $scope.events.modifiable = null; }; /** @@ -242,7 +241,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', $scope.removeSlotToPlace = function (e) { e.preventDefault(); if (typeof $scope.onSlotModifyUnselect === 'function') { $scope.onSlotModifyUnselect(); } - return $scope.events.placable = null; + $scope.events.placable = null; }; /** @@ -251,7 +250,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', */ $scope.tagMissmatch = function () { if ($scope.events.placable.tag_ids.length === 0) { return false; } - for (let tag of Array.from($scope.events.modifiable.tags)) { + for (const tag of Array.from($scope.events.modifiable.tags)) { if (!Array.from($scope.events.placable.tag_ids).includes(tag.id)) { return true; } @@ -271,7 +270,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', } return false; - } + }; /* PRIVATE SCOPE */ @@ -322,16 +321,16 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', size: 'md', controller: 'ReserveSlotTagsMismatchController', resolve: { - slotTags: function() { return slot.tags; }, + slotTags: function () { return slot.tags; }, userTags: function () { return $scope.user.tags; }, userName: function () { return $scope.user.name; } } }); - modalInstance.result.then(function(res) { + modalInstance.result.then(function (res) { if (typeof callback === 'function') callback(res); }); } - } + }; /** * Validates that no other reservations were made that conflict the current slot and alert the user about the conflict. @@ -346,12 +345,12 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', 'space_reservations', 'events_reservations' ].map(function (k) { - return _.filter($scope.user[k], function(r) { + return _.filter($scope.user[k], function (r) { return slot.start.isSame(r.start_at) || (slot.end.isAfter(r.start_at) && slot.end.isBefore(r.end_at)) || (slot.start.isAfter(r.start_at) && slot.start.isBefore(r.end_at)) || (slot.start.isBefore(r.start_at) && slot.end.isAfter(r.end_at)); - }) + }); }); sameTimeReservations = _.union.apply(null, sameTimeReservations); if (sameTimeReservations.length > 0) { @@ -361,17 +360,17 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', size: 'md', controller: 'ReserveSlotSameTimeController', resolve: { - sameTimeReservations: function() { return sameTimeReservations; }, + sameTimeReservations: function () { return sameTimeReservations; }, bookOverlappingSlotsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'book_overlapping_slots' }).$promise; }] } }); - modalInstance.result.then(function(res) { + modalInstance.result.then(function (res) { if (typeof callback === 'function') callback(res); }); } else { if (typeof callback === 'function') callback(); } - } + }; /** * Callback triggered when the selected slot changed @@ -381,13 +380,13 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', // if this slot is restricted for subscribers... if ($scope.slot.plan_ids.length > 0) { // ... we select all the plans matching these restrictions... - const _plans = _.filter($scope.plans, function (p) { return _.include($scope.slot.plan_ids, p.id) }); + const _plans = _.filter($scope.plans, function (p) { return _.includes($scope.slot.plan_ids, p.id); }); // ... and we group these plans, by Group... $scope.slot.plansGrouped = []; $scope.slot.group_ids = []; - for (let group of Array.from($scope.groups)) { + for (const group of Array.from($scope.groups)) { const groupObj = { id: group.id, name: group.name, plans: [] }; - for (let plan of Array.from(_plans)) { + for (const plan of Array.from(_plans)) { if (plan.group_id === group.id) { groupObj.plans.push(plan); } } if (groupObj.plans.length > 0) { @@ -400,13 +399,13 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', } } } - $scope.slot.group_ids = $scope.slot.plansGrouped.map(function(g) { return g.id; }); + $scope.slot.group_ids = $scope.slot.plansGrouped.map(function (g) { return g.id; }); } if (!$scope.slot.is_reserved && !$scope.events.modifiable && !$scope.slot.is_completed) { // slot is not reserved and we are not currently modifying a slot // -> can be added to cart or removed if already present - const index = $scope.events.reserved.indexOf($scope.slot); + const index = _.findIndex($scope.events.reserved, (e) => e._id === $scope.slot._id); if (index === -1) { if (($scope.limitToOneSlot === 'true') && $scope.events.reserved[0]) { // if we limit the number of slots in the cart to 1, and there is already @@ -437,7 +436,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', } else if ($scope.slot.is_reserved && $scope.events.modifiable && ($scope.slot.is_reserved._id === $scope.events.modifiable._id)) { // slot is reserved and currently modified // -> we cancel the modification - return $scope.cancelModifySlot(); + $scope.cancelModifySlot(); } else if ($scope.slot.is_reserved && (slotCanBeModified($scope.slot) || slotCanBeCanceled($scope.slot)) && !$scope.events.modifiable && ($scope.events.reserved.length === 0)) { // slot is reserved and is ok to be modified or cancelled // but we are not currently running a modification or having any slots in the cart @@ -456,9 +455,9 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', // the user has chosen an action, so we proceed if (type === 'move') { if (typeof $scope.onSlotStartToModify === 'function') { $scope.onSlotStartToModify(); } - return $scope.events.modifiable = $scope.slot; + $scope.events.modifiable = $scope.slot; } else if (type === 'cancel') { - return dialogs.confirm( + dialogs.confirm( { resolve: { object () { @@ -470,7 +469,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', } }, function () { // cancel confirmed - Slot.cancel({ id: $scope.slot.id }, function () { // successfully canceled + Slot.cancel({ id: $scope.slot.slot_id }, function () { // successfully canceled growl.success(_t('app.shared.cart.reservation_was_cancelled_successfully')); if (typeof $scope.onSlotCancelSuccess === 'function') { return $scope.onSlotCancelSuccess(); } } @@ -494,7 +493,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', $scope.events.moved = null; $scope.events.paid = []; $scope.events.modifiable = null; - return $scope.events.placable = null; + $scope.events.placable = null; }; /** @@ -666,7 +665,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', }; } ] - }).result['finally'](null).then(function (reservation) { afterPayment(reservation); }); + }).result.finally(null).then(function (reservation) { afterPayment(reservation); }); }; /** * Open a modal window that allows the user to process a local payment for his current shopping cart (admin only). @@ -735,7 +734,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); }; } ] - }).result['finally'](null).then(function (reservation) { afterPayment(reservation); }); + }).result.finally(null).then(function (reservation) { afterPayment(reservation); }); }; /** * Actions to run after the payment was successful @@ -756,22 +755,22 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', /** * Actions to pay slots */ - const paySlots = function() { + const paySlots = function () { const reservation = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan); return Wallet.getWalletByUser({ user_id: $scope.user.id }, function (wallet) { const amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount); - if ((AuthService.isAuthorized(['member']) && amountToPay > 0) - || (AuthService.isAuthorized('manager') && $scope.user.id === $rootScope.currentUser.id && amountToPay > 0)) { + if ((AuthService.isAuthorized(['member']) && amountToPay > 0) || + (AuthService.isAuthorized('manager') && $scope.user.id === $rootScope.currentUser.id && amountToPay > 0)) { if ($scope.settings.online_payment_module !== 'true') { growl.error(_t('app.shared.cart.online_payment_disabled')); } else { return payByStripe(reservation); } } else { - if (AuthService.isAuthorized(['admin']) - || (AuthService.isAuthorized('manager') && $scope.user.id !== $rootScope.currentUser.id) - || amountToPay === 0) { + if (AuthService.isAuthorized(['admin']) || + (AuthService.isAuthorized('manager') && $scope.user.id !== $rootScope.currentUser.id) || + amountToPay === 0) { return payOnSite(reservation); } } @@ -798,17 +797,16 @@ Application.Controllers.controller('ReserveSlotSameTimeController', ['$scope', ' */ $scope.ok = function () { $uibModalInstance.close({}); - } + }; /** * Cancellation callback */ $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); - } + }; } ]); - /** * Controller of the modal showing the slot tags */ @@ -823,13 +821,13 @@ Application.Controllers.controller('ReserveSlotTagsMismatchController', ['$scope */ $scope.ok = function () { $uibModalInstance.close({}); - } + }; /** * Cancellation callback */ $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); - } + }; } ]); @@ -845,12 +843,12 @@ Application.Controllers.controller('ReserveSlotWithoutPlanController', ['$scope' */ $scope.ok = function () { $uibModalInstance.close({}); - } + }; /** * Cancellation callback */ $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); - } + }; } ]); diff --git a/app/views/api/availabilities/machine.json.jbuilder b/app/views/api/availabilities/machine.json.jbuilder index c6fc966b5..6570c03fe 100644 --- a/app/views/api/availabilities/machine.json.jbuilder +++ b/app/views/api/availabilities/machine.json.jbuilder @@ -1,7 +1,7 @@ # frozen_string_literal: true json.array!(@slots) do |slot| - json.id slot.id if slot.id + json.slot_id slot.id if slot.id json.can_modify slot.can_modify json.title slot.title json.start slot.start_at.iso8601