1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-12-04 15:24:23 +01:00
fab-manager/app/assets/javascripts/controllers/admin/calendar.js.erb

970 lines
34 KiB
Plaintext
Raw Normal View History

/* eslint-disable
camelcase,
handle-callback-err,
no-return-assign,
no-undef,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
2018-11-21 11:08:53 +01:00
'use strict';
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
/**
* Controller used in the calendar management page
*/
2016-03-23 18:39:41 +01:00
2020-02-24 10:33:27 +01:00
Application.Controllers.controller('AdminCalendarController', ['$scope', '$state', '$uibModal', 'moment', 'Availability', 'Slot', 'Setting', 'Export', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', '_t', 'uiCalendarConfig', 'CalendarConfig', 'Member', 'uiTourService',
function ($scope, $state, $uibModal, moment, Availability, Slot, Setting, Export, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, _t, uiCalendarConfig, CalendarConfig, Member, uiTourService) {
/* PRIVATE STATIC CONSTANTS */
2016-03-23 18:39:41 +01:00
// The calendar is divided in slots of 30 minutes
2018-11-21 11:08:53 +01:00
const BASE_SLOT = '00:30:00';
2016-03-23 18:39:41 +01:00
// The bookings can be positioned every half hours
2018-11-21 11:08:53 +01:00
const BOOKING_SNAP = '00:30:00';
2016-03-23 18:39:41 +01:00
// We do not allow the creation of slots that are not a multiple of 60 minutes
const SLOT_MULTIPLE = Fablab.slotDuration;
2016-03-23 18:39:41 +01:00
/* PUBLIC SCOPE */
2018-11-19 16:17:49 +01:00
// list of the FabLab machines
2018-11-21 11:08:53 +01:00
$scope.machines = machinesPromise;
2018-11-19 16:17:49 +01:00
// currently selected availability
2018-11-21 11:08:53 +01:00
$scope.availability = null;
// corresponding fullCalendar item in the DOM
$scope.availabilityDom = null;
2018-11-19 16:17:49 +01:00
// bind the availabilities slots with full-Calendar events
2018-11-21 11:08:53 +01:00
$scope.eventSources = [];
$scope.eventSources.push({
url: '/api/availabilities',
textColor: 'black'
2018-11-21 11:08:53 +01:00
});
2018-11-19 16:17:49 +01:00
// fullCalendar (v2) configuration
$scope.calendarConfig = CalendarConfig({
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')),
select (start, end, jsEvent, view) {
2018-11-21 11:08:53 +01:00
return calendarSelectCb(start, end, jsEvent, view);
},
eventClick (event, jsEvent, view) {
2018-11-21 11:08:53 +01:00
return calendarEventClickCb(event, jsEvent, view);
},
eventRender (event, element, view) {
return eventRenderCb(event, element, view);
},
viewRender(view, element) {
return viewRenderCb(view, element);
},
loading (isLoading, view) {
2018-11-21 11:08:53 +01:00
return loadingCb(isLoading, view);
}
2018-11-21 11:08:53 +01:00
});
2018-11-19 16:17:49 +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
*/
2018-11-21 10:59:07 +01:00
$scope.cancelBooking = function (slot) {
// open a confirmation dialog
2018-11-20 12:26:06 +01:00
dialogs.confirm(
{
resolve: {
object () {
return {
2019-12-16 16:54:40 +01:00
title: _t('app.admin.calendar.confirmation_required'),
msg: _t('app.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 })
2018-11-21 11:08:53 +01:00
};
}
}
2018-11-20 12:26:06 +01:00
},
2018-11-21 10:59:07 +01:00
function () {
2018-11-20 12:26:06 +01:00
// the admin has confirmed, cancel the subscription
Slot.cancel(
{ id: slot.slot_id },
function (data, status) { // success
// update the canceled_at attribute
for (let resa of Array.from($scope.reservations)) {
if (resa.slot_id === data.id) {
2018-11-21 11:08:53 +01:00
resa.canceled_at = data.canceled_at;
break;
2018-11-20 12:26:06 +01:00
}
}
2018-11-20 12:26:06 +01:00
// notify the admin
2019-12-16 16:54:40 +01:00
return growl.success(_t('app.admin.calendar.reservation_was_successfully_cancelled'));
2018-11-20 12:26:06 +01:00
},
function (data, status) { // failed
2019-12-16 16:54:40 +01:00
growl.error(_t('app.admin.calendar.reservation_cancellation_failed'));
}
2018-11-21 11:08:53 +01:00
);
2018-11-20 12:26:06 +01:00
}
2018-11-21 11:08:53 +01:00
);
};
2018-11-19 16:17:49 +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 = function (machine) {
if ($scope.availability.machine_ids.length === 1) {
2019-12-16 16:54:40 +01:00
return growl.error(_t('app.admin.calendar.unable_to_remove_the_last_machine_of_the_slot_delete_the_slot_rather'));
} else {
// open a confirmation dialog
return dialogs.confirm({
resolve: {
object () {
return {
2019-12-16 16:54:40 +01:00
title: _t('app.admin.calendar.confirmation_required'),
msg: _t('app.admin.calendar.do_you_really_want_to_remove_MACHINE_from_this_slot', { GENDER: getGender($scope.currentUser), MACHINE: machine.name }) + ' ' +
_t('app.admin.calendar.this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing') + '<br><strong>' +
_t('app.admin.calendar.beware_this_cannot_be_reverted') + '</strong>'
2018-11-21 11:08:53 +01:00
};
}
}
}
, function () {
// the admin has confirmed, remove the machine
2018-11-21 11:08:53 +01:00
const machines = $scope.availability.machine_ids;
for (let m_id = 0; m_id < machines.length; m_id++) {
2018-11-21 11:08:53 +01:00
const key = machines[m_id];
if (m_id === machine.id) {
2018-11-21 11:08:53 +01:00
machines.splice(key, 1);
}
}
return Availability.update({ id: $scope.availability.id }, { availability: { machines_attributes: [{ id: machine.id, _destroy: true }] } }
, function (data, status) { // success
// update the machine_ids attribute
2018-11-21 11:08:53 +01:00
$scope.availability.machine_ids = data.machine_ids;
$scope.availability.title = data.title;
uiCalendarConfig.calendars.calendar.fullCalendar('rerenderEvents');
// notify the admin
2019-12-16 16:54:40 +01:00
return growl.success(_t('app.admin.calendar.the_machine_was_successfully_removed_from_the_slot'));
}
2018-11-21 10:59:07 +01:00
, function (data, status) { // failed
2019-12-16 16:54:40 +01:00
growl.error(_t('app.admin.calendar.deletion_failed'));
2018-11-20 12:26:06 +01:00
}
2018-11-21 11:08:53 +01:00
);
});
}
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* Callback to alert the admin that the export request was acknowledged and is
* processing right now.
*/
2018-11-21 10:59:07 +01:00
$scope.alertExport = function (type) {
Export.status({ category: 'availabilities', type }).then(function (res) {
if (!res.data.exists) {
2019-12-16 16:54:40 +01:00
return growl.success(_t('app.admin.calendar.export_is_running_you_ll_be_notified_when_its_ready'));
}
2018-11-21 11:08:53 +01:00
});
};
2018-11-19 16:17:49 +01:00
/**
* Mark the selected slot as unavailable for new reservations or allow reservations again on it
*/
$scope.toggleLockReservations = function () {
// first, define a shortcut to the lock property
2018-11-21 11:08:53 +01:00
const locked = $scope.availability.lock;
// then check if we'll allow reservations locking
2018-11-21 11:08:53 +01:00
let prevent = !locked; // if currently locked, allow unlock anyway
if (!locked) {
2018-11-21 11:08:53 +01:00
prevent = false;
angular.forEach($scope.reservations, function (r) {
if (r.canceled_at === null) {
2018-11-21 11:08:53 +01:00
return prevent = true;
}
2018-11-21 11:08:53 +01:00
}); // if currently unlocked and has any non-cancelled reservation, disallow locking
}
if (!prevent) {
// open a confirmation dialog
2018-11-20 12:26:06 +01:00
dialogs.confirm(
{
resolve: {
object () {
return {
2019-12-16 16:54:40 +01:00
title: _t('app.admin.calendar.confirmation_required'),
msg: locked ? _t('app.admin.calendar.do_you_really_want_to_allow_reservations') : _t('app.admin.calendar.do_you_really_want_to_block_this_slot')
2018-11-21 11:08:53 +01:00
};
}
}
2018-11-20 12:26:06 +01:00
},
2018-11-21 10:59:07 +01:00
function () {
2018-11-20 12:26:06 +01:00
// the admin has confirmed, lock/unlock the slot
Availability.lock(
{ id: $scope.availability.id },
{ lock: !locked },
function (data) { // success
2018-11-21 11:08:53 +01:00
$scope.availability = data;
2019-12-16 16:54:40 +01:00
growl.success(locked ? _t('app.admin.calendar.unlocking_success') : _t('app.admin.calendar.locking_success'));
2018-11-21 11:08:53 +01:00
uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents');
2018-11-20 12:26:06 +01:00
},
function (error) { // failed
2019-12-16 16:54:40 +01:00
growl.error(locked ? _t('app.admin.calendar.unlocking_failed') : _t('app.admin.calendar.locking_failed'));
2018-11-21 11:08:53 +01:00
console.error(error);
2018-11-20 12:26:06 +01:00
}
2018-11-21 11:08:53 +01:00
);
}
2018-11-21 11:08:53 +01:00
);
} else {
2019-12-16 16:54:40 +01:00
return growl.error(_t('app.admin.calendar.unlockable_because_reservations'));
}
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* Confirm and destroy the slot in $scope.availability
*/
2018-11-21 10:59:07 +01:00
$scope.removeSlot = function () {
2018-11-20 12:26:06 +01:00
// open a confirmation dialog
2019-11-18 17:29:51 +01:00
const modalInstance = $uibModal.open({
animation: true,
templateUrl: '<%= asset_path "admin/calendar/deleteRecurrent.html" %>',
size: 'md',
controller: 'DeleteRecurrentAvailabilityController',
resolve: {
availabilityPromise: ['Availability', function (Availability) { return Availability.get({ id: $scope.availability.id }).$promise; }]
}
2019-11-18 17:29:51 +01:00
});
// once the dialog was closed, do things depending on the result
modalInstance.result.then(function (res) {
if (res.status == 'success') {
$scope.availability = null;
}
for (const availability of res.availabilities) {
uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents', availability);
}
});
2018-11-21 11:08:53 +01:00
};
2020-02-24 10:33:27 +01:00
/**
* Setup the feature-tour for the admin/calendar page.
* This is intended as a contextual help (when pressing F1)
*/
$scope.setupCalendarTour = function () {
// get the tour defined by the ui-tour directive
const uitour = uiTourService.getTourByName('calendar');
uitour.createStep({
selector: 'body',
stepId: 'welcome',
order: 0,
title: _t('app.admin.tour.calendar.welcome.title'),
content: _t('app.admin.tour.calendar.welcome.content'),
placement: 'bottom',
orphan: true
});
uitour.createStep({
selector: '.admin-calendar .fc-view-container',
stepId: 'agenda',
order: 1,
title: _t('app.admin.tour.calendar.agenda.title'),
content: _t('app.admin.tour.calendar.agenda.content'),
placement: 'right'
});
uitour.createStep({
selector: '.admin-calendar .export-xls-button',
stepId: 'export',
order: 2,
title: _t('app.admin.tour.calendar.export.title'),
content: _t('app.admin.tour.calendar.export.content'),
placement: 'left'
});
uitour.createStep({
selector: '.heading .import-ics-button',
stepId: 'import',
order: 3,
title: _t('app.admin.tour.calendar.import.title'),
content: _t('app.admin.tour.calendar.import.content'),
placement: 'left'
});
uitour.createStep({
selector: 'body',
stepId: 'conclusion',
order: 4,
title: _t('app.admin.tour.conclusion.title'),
content: _t('app.admin.tour.conclusion.content'),
placement: 'bottom',
orphan: true
});
// on tour end, save the status in database
uitour.on('ended', function () {
if (uitour.getStatus() === uitour.Status.ON && $scope.currentUser.profile.tours.indexOf('calendar') < 0) {
Member.completeTour({ id: $scope.currentUser.id }, { tour: 'calendar' }, function (res) {
$scope.currentUser.profile.tours = res.tours;
});
}
});
// if the user has never seen the tour, show him now
2020-03-02 11:17:28 +01:00
if (Fablab.featureTourDisplay !== 'manual' && $scope.currentUser.profile.tours.indexOf('calendar') < 0) {
2020-02-24 10:33:27 +01:00
uitour.start();
}
// start this tour when an user press F1 - this is contextual help
window.addEventListener('keydown', handleF1);
}
/* PRIVATE SCOPE */
2020-02-24 10:33:27 +01:00
/**
* Kind of constructor: these actions will be realized first when the controller is loaded
*/
const initialize = function () {
// listen the $destroy event of the controller to remove the F1 key binding
$scope.$on('$destroy', function () {
window.removeEventListener('keydown', handleF1);
});
};
2018-11-19 16:17:49 +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'
*/
2020-02-24 10:33:27 +01:00
const getGender = function (user) {
if (user.statistic_profile) {
if (user.statistic_profile.gender === 'true') { return 'male'; } else { return 'female'; }
2018-11-21 11:08:53 +01:00
} else { return 'other'; }
};
// Triggered when the admin drag on the agenda to create a new reservable slot.
// @see http://fullcalendar.io/docs/selection/select_callback/
2018-11-21 10:59:07 +01:00
//
2020-02-24 10:33:27 +01:00
const calendarSelectCb = function (start, end, jsEvent, view) {
2018-11-21 11:08:53 +01:00
start = moment.tz(start.toISOString(), Fablab.timezone);
end = moment.tz(end.toISOString(), Fablab.timezone);
// check if slot is not in the past
const today = new Date();
if (Math.trunc((start.valueOf() - today) / (60 * 1000)) < 0) {
2019-12-16 16:54:40 +01:00
growl.warning(_t('app.admin.calendar.event_in_the_past'));
return uiCalendarConfig.calendars.calendar.fullCalendar('unselect');
}
// check that the selected slot is an multiple of SLOT_MULTIPLE (ie. not decimal)
const slots = Math.trunc((end.valueOf() - start.valueOf()) / (60 * 1000)) / SLOT_MULTIPLE;
if (!Number.isInteger(slots)) {
// otherwise, round it to upper decimal
const upper = Math.ceil(slots) * SLOT_MULTIPLE;
end = moment(start).add(upper, 'minutes');
}
// then we open a modal window to let the admin specify the slot type
const modalInstance = $uibModal.open({
templateUrl: '<%= asset_path "admin/calendar/eventModal.html" %>',
controller: 'CreateEventModalController',
resolve: {
start () { return start; },
end () { return end; },
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }],
2019-11-13 10:59:10 +01:00
spacesPromise: ['Space', function (Space) { return Space.query().$promise; }],
tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }]
} });
// when the modal is closed, we send the slot to the server for saving
modalInstance.result.then(
function (availability) {
uiCalendarConfig.calendars.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,
tags: availability.tags,
machine_ids: availability.machine_ids
2018-11-20 12:26:06 +01:00
},
true
2018-11-21 11:08:53 +01:00
);
},
function () { uiCalendarConfig.calendars.calendar.fullCalendar('unselect'); }
);
2016-03-23 18:39:41 +01:00
2018-11-21 11:08:53 +01:00
return uiCalendarConfig.calendars.calendar.fullCalendar('unselect');
};
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
/**
* Triggered when the admin clicks on a availability slot in the agenda.
* @see http://fullcalendar.io/docs/mouse/eventClick/
*/
2020-02-24 10:33:27 +01:00
const calendarEventClickCb = function (event, jsEvent, view) {
2018-11-21 11:08:53 +01:00
$scope.availability = event;
if ($scope.availabilityDom) {
$scope.availabilityDom.classList.remove("fc-selected")
}
$scope.availabilityDom = jsEvent.target.closest('.fc-event');
$scope.availabilityDom.classList.add("fc-selected")
// if the user has clicked on the delete event button, delete the event
if ($(jsEvent.target).hasClass('remove-event')) {
2018-11-21 11:08:53 +01:00
return $scope.removeSlot();
// if the user has only clicked on the event, display its reservations
} else {
2018-11-21 11:08:53 +01:00
return Availability.reservations({ id: event.id }, function (reservations) { $scope.reservations = reservations; });
}
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* 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/
*/
2020-02-24 10:33:27 +01:00
const eventRenderCb = function (event, element) {
if (event.available_type !== 'event') {
element.find('.fc-content').prepend('<span class="remove-event">x&nbsp;</span>');
}
if (event.tags.length > 0) {
2018-11-21 11:08:53 +01:00
let html = '';
for (let tag of Array.from(event.tags)) {
2018-11-21 11:08:53 +01:00
html += `<span class='label label-success text-white'>${tag.name}</span> `;
}
2018-11-21 11:08:53 +01:00
element.find('.fc-title').append(`<br/>${html}`);
}
// force return to prevent coffee-script auto-return to return random value (possiblity falsy)
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* Triggered when resource fetching starts/stops.
* @see https://fullcalendar.io/docs/resource_data/loading/
*/
const loadingCb = function (isLoading, view) {
if (isLoading) {
// we remove existing events when fetching starts to prevent duplicates
uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents');
}
2018-11-21 11:08:53 +01:00
};
/**
* Triggered when the view is changed
* @see https://fullcalendar.io/docs/v3/viewRender#v2
*/
const viewRenderCb = function(view, element) {
// we unselect the current event to keep consistency
$scope.availability = null;
$scope.availabilityDom = null;
};
2020-02-24 10:33:27 +01:00
/**
* Callback used to trigger the feature tour when the user press the F1 key.
* @param e {KeyboardEvent}
*/
const handleF1 = function (e) {
if (e.key === 'F1') {
e.preventDefault();
const tour = uiTourService.getTourByName('calendar');
if (tour) { tour.start(); }
}
};
// !!! MUST BE CALLED AT THE END of the controller
return initialize();
}
2016-03-23 18:39:41 +01:00
2018-11-21 11:08:53 +01:00
]);
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
/**
* Controller used in the slot creation modal window
*/
2019-11-13 10:59:10 +01:00
Application.Controllers.controller('CreateEventModalController', ['$scope', '$uibModalInstance', '$sce', 'moment', 'start', 'end', 'machinesPromise', 'Availability', 'trainingsPromise', 'spacesPromise', 'tagsPromise', 'growl', '_t',
function ($scope, $uibModalInstance, $sce, moment, start, end, machinesPromise, Availability, trainingsPromise, spacesPromise, tagsPromise, growl, _t) {
// $uibModal parameter
2018-11-21 11:08:53 +01:00
$scope.start = start;
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// $uibModal parameter
2018-11-21 11:08:53 +01:00
$scope.end = end;
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// machines list
2018-11-21 11:08:53 +01:00
$scope.machines = machinesPromise.filter(function (m) { return !m.disabled; });
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// trainings list
2018-11-21 11:08:53 +01:00
$scope.trainings = trainingsPromise.filter(function (t) { return !t.disabled; });
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// spaces list
2018-11-21 11:08:53 +01:00
$scope.spaces = spacesPromise.filter(function (s) { return !s.disabled; });
2019-11-13 10:59:10 +01:00
// all tags list
$scope.tags = tagsPromise;
2018-11-19 16:17:49 +01:00
// machines associated with the created slot
2018-11-21 11:08:53 +01:00
$scope.selectedMachines = [];
$scope.selectedMachinesBinding = {};
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// training associated with the created slot
2018-11-21 11:08:53 +01:00
$scope.selectedTraining = null;
2016-03-23 18:39:41 +01:00
2018-11-19 16:17:49 +01:00
// space associated with the created slot
2018-11-21 11:08:53 +01:00
$scope.selectedSpace = null;
2018-11-19 16:17:49 +01:00
// UI step
2018-11-21 11:08:53 +01:00
$scope.step = 1;
2018-11-19 16:17:49 +01:00
// the user is not able to edit the ending time of the availability, unless he set the type to 'training'
2018-11-21 11:08:53 +01:00
$scope.endDateReadOnly = true;
2018-11-19 16:17:49 +01:00
// timepickers configuration
$scope.timepickers = {
start: {
hstep: 1,
mstep: 5
},
end: {
hstep: 1,
mstep: 5
}
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
// slot details
$scope.availability = {
start_at: start,
end_at: end,
available_type: 'machines', // default
2019-11-13 10:59:10 +01:00
tag_ids: [],
is_recurrent: false,
period: 'week',
nb_periods: 1,
end_date: undefined // recurrence end
2018-11-21 11:08:53 +01:00
};
// recurrent slots
$scope.occurrences = [];
// localized name(s) of the reservable item(s)
$scope.reservableName = '';
2019-11-13 10:59:10 +01:00
// localized name(s) of the selected tag(s)
$scope.tagsName = '';
// make the duration available for display
$scope.slotDuration = Fablab.slotDuration;
2018-11-19 16:17:49 +01:00
/**
* Adds or removes the provided machine from the current slot
* @param machine {Object}
*/
$scope.toggleSelection = function (machine) {
2018-11-21 11:08:53 +01:00
const index = $scope.selectedMachines.indexOf(machine);
if (index > -1) {
2018-11-21 11:08:53 +01:00
return $scope.selectedMachines.splice(index, 1);
} else {
2018-11-21 11:08:53 +01:00
return $scope.selectedMachines.push(machine);
}
2018-11-21 11:08:53 +01:00
};
/**
* Select/unselect all the machines
*/
$scope.toggleAll = function() {
const count = $scope.selectedMachines.length;
$scope.selectedMachines = [];
$scope.selectedMachinesBinding = {};
if (count == 0) {
$scope.machines.forEach(function (machine) {
$scope.selectedMachines.push(machine);
$scope.selectedMachinesBinding[machine.id] = true;
})
}
}
2018-11-19 16:17:49 +01:00
/**
* Callback for the modal window validation: save the slot and closes the modal
*/
$scope.ok = function () {
if ($scope.availability.available_type === 'machines') {
if ($scope.selectedMachines.length > 0) {
2018-11-21 11:08:53 +01:00
$scope.availability.machine_ids = $scope.selectedMachines.map(function (m) { return m.id; });
} else {
2019-12-16 16:54:40 +01:00
growl.error(_t('app.admin.calendar.you_should_select_at_least_a_machine'));
2018-11-21 11:08:53 +01:00
return;
}
} else if ($scope.availability.available_type === 'training') {
2018-11-21 11:08:53 +01:00
$scope.availability.training_ids = [$scope.selectedTraining.id];
} else if ($scope.availability.available_type === 'space') {
2018-11-21 11:08:53 +01:00
$scope.availability.space_ids = [$scope.selectedSpace.id];
}
if ($scope.availability.is_recurrent) {
$scope.availability.occurrences = $scope.occurrences;
}
return Availability.save(
{ availability: $scope.availability },
function (availability) { $uibModalInstance.close(availability); }
);
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* Move the modal UI to the next step
*/
$scope.next = function () {
2018-11-21 11:08:53 +01:00
if ($scope.step === 1) { $scope.setNbTotalPlaces(); }
2019-11-12 14:25:28 +01:00
if ($scope.step === 2) { return validateSelection(); }
if ($scope.step === 4) { return validateRecurrence(); }
2018-11-21 11:08:53 +01:00
return $scope.step++;
};
2018-11-19 16:17:49 +01:00
/**
* Move the modal UI to the next step
*/
2018-11-21 11:08:53 +01:00
$scope.previous = function () { return $scope.step--; };
2018-11-19 16:17:49 +01:00
/**
* Callback to cancel the slot creation
*/
2018-11-21 11:08:53 +01:00
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
2018-11-19 16:17:49 +01:00
/**
* For training avaiabilities, set the maximum number of people allowed to register on this slot
*/
$scope.setNbTotalPlaces = function () {
if ($scope.availability.available_type === 'training') {
2018-11-21 11:08:53 +01:00
return $scope.availability.nb_total_places = $scope.selectedTraining.nb_total_places;
} else if ($scope.availability.available_type === 'space') {
2018-11-21 11:08:53 +01:00
return $scope.availability.nb_total_places = $scope.selectedSpace.default_places;
}
2018-11-21 11:08:53 +01:00
};
/* PRIVATE SCOPE */
2018-11-19 16:17:49 +01:00
/**
* Kind of constructor: these actions will be realized first when the controller is loaded
*/
const initialize = function () {
if ($scope.trainings.length > 0) {
2018-11-21 11:08:53 +01:00
$scope.selectedTraining = $scope.trainings[0];
}
if ($scope.spaces.length > 0) {
2018-11-21 11:08:53 +01:00
$scope.selectedSpace = $scope.spaces[0];
}
// When we configure a machine/space availability, do not let the user change the end time, as the total
// time must be dividable by Fablab.slotDuration minutes (base slot duration). For training availabilities, the user
2018-11-19 16:17:49 +01:00
// can configure any duration as it does not matters.
$scope.$watch('availability.available_type', function (newValue, oldValue, scope) {
if ((newValue === 'machines') || (newValue === 'space')) {
2018-11-21 11:08:53 +01:00
$scope.endDateReadOnly = true;
const slots = Math.trunc(($scope.end.valueOf() - $scope.start.valueOf()) / (60 * 1000)) / Fablab.slotDuration;
if (!Number.isInteger(slots)) {
// otherwise, round it to upper decimal
const upper = Math.ceil(slots) * Fablab.slotDuration;
$scope.end = moment($scope.start).add(upper, 'minutes').toDate();
}
2018-11-21 11:08:53 +01:00
return $scope.availability.end_at = $scope.end;
} else {
2018-11-21 11:08:53 +01:00
return $scope.endDateReadOnly = false;
}
2018-11-21 11:08:53 +01:00
});
// When the start date is changed, if we are configuring a machine/space availability,
2018-11-19 16:17:49 +01:00
// maintain the relative length of the slot (ie. change the end time accordingly)
$scope.$watch('start', function (newValue, oldValue, scope) {
// for machine or space availabilities, adjust the end time
if (($scope.availability.available_type === 'machines') || ($scope.availability.available_type === 'space')) {
2018-11-21 11:08:53 +01:00
end = moment($scope.end);
end.add(moment(newValue).diff(oldValue), 'milliseconds');
$scope.end = end.toDate();
} else { // for training availabilities
2019-11-12 14:25:28 +01:00
// prevent the admin from setting the beginning after the end
if (moment(newValue).add(Fablab.slotDuration, 'minutes').isAfter($scope.end)) {
2018-11-21 11:08:53 +01:00
$scope.start = oldValue;
}
}
// update availability object
2020-03-02 16:20:50 +01:00
$scope.availability.start_at = $scope.start;
2018-11-21 11:08:53 +01:00
});
2018-11-19 16:17:49 +01:00
// Maintain consistency between the end time and the date object in the availability object
2020-03-02 16:20:50 +01:00
$scope.$watch('end', function (newValue, oldValue, scope) {
2019-11-12 14:25:28 +01:00
// we prevent the admin from setting the end of the availability before its beginning
if (moment($scope.start).add(Fablab.slotDuration, 'minutes').isAfter(newValue)) {
2018-11-21 11:08:53 +01:00
$scope.end = oldValue;
}
// update availability object
2020-03-02 16:20:50 +01:00
$scope.availability.end_at = $scope.end;
2018-11-21 11:08:53 +01:00
});
};
2019-11-12 14:25:28 +01:00
/**
* Validates that a machine or more was/were selected before continuing to step 3 (adjust time + tags)
*/
const validateSelection = function () {
if ($scope.availability.available_type === 'machines') {
if ($scope.selectedMachines.length === 0) {
2019-12-16 16:54:40 +01:00
return growl.error(_t('app.admin.calendar.you_should_select_at_least_a_machine'));
2019-11-12 14:25:28 +01:00
}
}
$scope.step++;
};
/**
* Validates that the recurrence parameters were correctly set before continuing to step 5 (summary)
*/
const validateRecurrence = function () {
if ($scope.availability.is_recurrent) {
if (!$scope.availability.period) {
2019-12-16 16:54:40 +01:00
return growl.error(_t('app.admin.calendar.select_period'));
2019-11-12 14:25:28 +01:00
}
if (!$scope.availability.nb_periods) {
2019-12-16 16:54:40 +01:00
return growl.error(_t('app.admin.calendar.select_nb_period'));
2019-11-12 14:25:28 +01:00
}
if (!$scope.availability.end_date) {
2019-12-16 16:54:40 +01:00
return growl.error(_t('app.admin.calendar.select_end_date'));
2019-11-12 14:25:28 +01:00
}
}
// settings are ok
computeOccurrences();
2019-11-13 10:59:10 +01:00
computeNames();
2019-11-12 14:25:28 +01:00
$scope.step++;
};
/**
* Compute the various occurrences of the availability, according to the recurrence settings
*/
const computeOccurrences = function () {
$scope.occurrences = [];
if ($scope.availability.is_recurrent) {
const date = moment($scope.availability.start_at);
2019-11-18 11:50:20 +01:00
const diff = moment($scope.availability.end_at).diff($scope.availability.start_at);
const end = moment($scope.availability.end_date).endOf('day');
while (date.isBefore(end)) {
const occur_end = moment(date).add(diff, 'ms');
$scope.occurrences.push({
start_at: date.toDate(),
end_at: occur_end.toDate()
});
date.add($scope.availability.nb_periods, $scope.availability.period);
}
} else {
$scope.occurrences.push({
start_at: $scope.availability.start_at,
end_at: $scope.availability.end_at
});
}
};
2019-11-13 10:59:10 +01:00
const computeNames = function () {
$scope.reservableName = '';
switch ($scope.availability.available_type) {
case 'machines':
2019-11-13 10:59:10 +01:00
$scope.reservableName = localizedList($scope.selectedMachines)
break;
case 'training':
2019-11-13 10:59:10 +01:00
$scope.reservableName = `<strong>${$scope.selectedTraining.name}</strong>`;
break;
case 'space':
2019-11-13 10:59:10 +01:00
$scope.reservableName = `<strong>${$scope.selectedSpace.name}</strong>`;
break;
default:
2019-12-16 16:54:40 +01:00
$scope.reservableName = `<span class="warning">${_t("app.admin.calendar.none")}</span>`;
}
2019-11-13 10:59:10 +01:00
const tags = $scope.tags.filter(function (t) {
return $scope.availability.tag_ids.indexOf(t.id) > -1;
})
$scope.tagsName = localizedList(tags);
}
const localizedList = function (items) {
2019-12-16 16:54:40 +01:00
if (items.length === 0) return `<span class="text-gray text-italic">${_t("app.admin.calendar.none")}</span>`;
2019-11-13 10:59:10 +01:00
const names = items.map(function (i) { return $sce.trustAsHtml(`<strong>${i.name}</strong>`); });
if (items.length > 1) return names.slice(0, -1).join(', ') + ` ${_t('app.admin.calendar.and')} ` + names[names.length - 1];
2019-11-13 10:59:10 +01:00
return names[0];
}
2018-11-19 16:17:49 +01:00
// !!! MUST BE CALLED AT THE END of the controller
2018-11-21 11:08:53 +01:00
return initialize();
}
2018-11-21 11:08:53 +01:00
]);
2019-11-18 17:29:51 +01:00
/**
* Controller used in the slot deletion modal window
*/
Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$scope', '$uibModalInstance', 'Availability', 'availabilityPromise', 'growl', '_t',
function ($scope, $uibModalInstance, Availability, availabilityPromise, growl, _t) {
// is the current slot (to be deleted) recurrent?
$scope.isRecurrent = availabilityPromise.is_recurrent;
// with recurrent slots: how many slots should we delete?
$scope.deleteMode = 'single';
/**
* Confirmation callback
*/
$scope.ok = function () {
const { id, start_at, end_at } = availabilityPromise;
// the admin has confirmed, delete the slot
Availability.delete(
{ id, mode: $scope.deleteMode },
function (res) {
// delete success
if (res.deleted > 1) {
growl.success(_t(
2019-12-16 16:54:40 +01:00
'app.admin.calendar.slots_deleted',
{START: moment(start_at).format('LL LT'), COUNT: res.deleted - 1}
2019-11-18 17:29:51 +01:00
));
} else {
growl.success(_t(
2019-12-16 16:54:40 +01:00
'app.admin.calendar.slot_successfully_deleted',
2019-11-18 17:29:51 +01:00
{START: moment(start_at).format('LL LT'), END: moment(end_at).format('LT')}
));
}
$uibModalInstance.close({
status: 'success',
availabilities: res.details.map(function (d) { return d.availability.id })
});
},
function (res) {
// not everything was deleted
const { data } = res;
if (data.total > 1) {
growl.warning(_t(
2019-12-16 16:54:40 +01:00
'app.admin.calendar.slots_not_deleted',
{TOTAL: data.total, COUNT: data.total - data.deleted}
2019-11-18 17:29:51 +01:00
));
} else {
growl.error(_t(
2019-12-16 16:54:40 +01:00
'app.admin.calendar.unable_to_delete_the_slot',
2019-11-18 17:29:51 +01:00
{START: moment(start_at).format('LL LT'), END: moment(end_at).format('LT')}
));
}
$uibModalInstance.close({
status: 'failed',
availabilities: data.details.filter(function (d) { return d.status }).map(function (d) { return d.availability.id })
});
});
}
/**
* Cancellation callback
*/
$scope.cancel = function () {
$uibModalInstance.dismiss('cancel');
}
}
]);
2019-11-27 16:17:32 +01:00
/**
* Controller used in the iCalendar (ICS) imports management page
*/
Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCalendars', 'ICalendar', 'dialogs', 'growl', '_t',
function ($scope, iCalendars, ICalendar, dialogs, growl, _t) {
2019-11-27 16:17:32 +01:00
// list of ICS sources
2019-11-27 17:05:19 +01:00
$scope.calendars = iCalendars;
2019-11-27 16:17:32 +01:00
// configuration of a new ICS source
$scope.newCalendar = {
color: undefined,
2019-12-03 10:17:29 +01:00
text_color: undefined,
2019-11-27 16:17:32 +01:00
url: undefined,
2019-11-27 17:39:19 +01:00
name: undefined,
2019-12-03 10:17:29 +01:00
text_hidden: false
2019-11-27 16:17:32 +01:00
};
/**
* Save the new iCalendar in database
*/
$scope.save = function () {
2019-11-27 17:39:19 +01:00
ICalendar.save({}, { i_calendar: $scope.newCalendar }, function (data) {
// success
$scope.calendars.push(data);
$scope.newCalendar.url = undefined;
$scope.newCalendar.name = undefined;
$scope.newCalendar.color = null;
2019-12-03 10:17:29 +01:00
$scope.newCalendar.text_color = null;
$scope.newCalendar.text_hidden = false;
2019-11-27 17:39:19 +01:00
}, function (error) {
// failed
2019-12-16 16:54:40 +01:00
growl.error(_t('app.admin.icalendar.create_error'));
2019-11-27 17:39:19 +01:00
console.error(error);
})
2019-11-27 16:17:32 +01:00
}
/**
* Return a CSS-like style of the given calendar configuration
* @param calendar
*/
$scope.calendarStyle = function (calendar) {
return {
'border-color': calendar.color,
2019-12-03 10:17:29 +01:00
'color': calendar.text_color,
'width': calendar.text_hidden ? '50px' : 'auto',
'height': calendar.text_hidden ? '21px' : 'auto'
2019-11-27 16:17:32 +01:00
};
}
/**
* Delete the given calendar from the database
* @param calendar
*/
$scope.delete = function (calendar) {
dialogs.confirm(
{
resolve: {
object () {
return {
2019-12-16 16:54:40 +01:00
title: _t('app.admin.icalendar.confirmation_required'),
msg: _t('app.admin.icalendar.confirm_delete_import')
};
}
}
},
2019-12-02 12:19:30 +01:00
function () {
ICalendar.delete(
{ id: calendar.id },
function () {
// success
const idx = $scope.calendars.indexOf(calendar);
$scope.calendars.splice(idx, 1);
2019-12-16 16:54:40 +01:00
growl.info(_t('app.admin.icalendar.delete_success'));
}, function (error) {
// failed
2019-12-16 16:54:40 +01:00
growl.error(_t('app.admin.icalendar.delete_failed'));
console.error(error);
}
);
}
)
2019-12-02 12:19:30 +01:00
}
/**
* Asynchronously re-fetches the events from the given calendar
* @param calendar
*/
$scope.sync = function (calendar) {
ICalendar.sync(
{ id: calendar.id },
function () {
// success
2019-12-16 16:54:40 +01:00
growl.info(_t('app.admin.icalendar.refresh'));
2019-12-02 12:19:30 +01:00
}, function (error) {
// failed
2019-12-16 16:54:40 +01:00
growl.error(_t('app.admin.icalendar.sync_failed'));
2019-12-02 12:19:30 +01:00
console.error(error);
}
)
2019-11-27 16:17:32 +01:00
}
}
]);