diff --git a/app/components/offering-editor.js b/app/components/offering-editor.js index 77a136c1b1..e430d60706 100644 --- a/app/components/offering-editor.js +++ b/app/components/offering-editor.js @@ -11,13 +11,16 @@ export default Component.extend({ init() { this._super(...arguments); - this.setProperties({ - instructors: [], - instructorGroups: [], - learnerGroups: [], - startDate: moment().format('MM/DD/YYYY'), - endDate: moment().add(1, 'days').format('MM/DD/YYYY') - }); + const instructors = []; + const instructorGroups = []; + const learnerGroups = []; + const startDate = new Date(); + const endDate = new Date(startDate); + endDate.setDate(startDate.getDate() + 1); + const startTime = new Date().setHours(8, 0, 0, 0); + const endTime = new Date().setHours(9, 0, 0, 0); + + this.setProperties({ instructors, instructorGroups, learnerGroups, startDate, endDate, startTime, endTime }); }, classNames: ['offering-editor'], @@ -27,6 +30,8 @@ export default Component.extend({ startDate: null, endDate: null, + startTime: null, + endTime: null, room: null, instructors: null, @@ -91,6 +96,45 @@ export default Component.extend({ }).sortBy('title'); }), + datesValidated() { + const resetStartDate = this.get('startDate').setHours(0, 0, 0, 0); + const resetEndDate = this.get('endDate').setHours(0, 0, 0, 0); + + let isEndDateOnOrBeforeStartDate = this.get('isMultiDay') && resetStartDate >= resetEndDate; + + return isEndDateOnOrBeforeStartDate ? false : true; + }, + + timesValidated() { + const startTime = this.get('startTime'); + const endTime = this.get('endTime'); + + let isEndTimeOnOrBeforeStartTime = !this.get('isMultiDay') && startTime >= endTime; + + return isEndTimeOnOrBeforeStartTime ? false : true; + }, + + calculateDateTimes() { + let startDate = moment(this.get('startDate')); + let endDate; + + let starTime = moment(this.get('startTime')); + startDate.hour(starTime.format('HH')); + startDate.minute(starTime.format('mm')); + + if (this.get('isMultiDay')){ + endDate = moment(this.get('endDate')); + } else { + endDate = startDate.clone(); + } + + let endTime = moment(this.get('endTime')); + endDate.hour(endTime.format('HH')); + endDate.minute(endTime.format('mm')); + + return { startDate, endDate }; + }, + actions: { setOfferingType(value) { this.set('singleOffering', value); @@ -102,20 +146,20 @@ export default Component.extend({ changeStartTime(date) { let newStart = moment(date); - let startDate = moment(this.get('startDate')); + let startTime = moment(this.get('startTime')); - startDate.hour(newStart.format('HH')); - startDate.minute(newStart.format('mm')); - this.set('startDate', startDate.toDate()); + startTime.hour(newStart.format('HH')); + startTime.minute(newStart.format('mm')); + this.set('startTime', startTime.toDate()); }, changeEndTime(date) { let newEnd = moment(date); - let endDate = moment(this.get('endDate')); + let endTime = moment(this.get('endTime')); - endDate.hour(newEnd.format('HH')); - endDate.minute(newEnd.format('mm')); - this.set('endDate', endDate.toDate()); + endTime.hour(newEnd.format('HH')); + endTime.minute(newEnd.format('mm')); + this.set('endTime', endTime.toDate()); }, addInstructor(instructor){ @@ -153,38 +197,33 @@ export default Component.extend({ }, create() { - let startDate = moment(this.get('startDate')); - let endDate; + if (this.datesValidated() && this.timesValidated()) { + let datesHash = this.calculateDateTimes(); - if (this.get('isMultiDay')){ - endDate = moment(this.get('endDate')); - } else { - endDate = startDate.clone(); - let endTime = moment(this.get('endDate')); - endDate.hour(endTime.format('HH')); - endDate.minute(endTime.format('mm')); - } + let params = { + startDate: datesHash.startDate.toDate(), + endDate: datesHash.endDate.toDate(), + learnerGroups: this.get('learnerGroups') + }; - let params = { - startDate: startDate.toDate(), - endDate: endDate.toDate(), - learnerGroups: this.get('learnerGroups') - }; + if (this.get('singleOffering')) { + params.room = this.get('room'); + params.instructors = this.get('instructors'); + params.instructorGroups = this.get('instructorGroups'); - if (this.get('singleOffering')) { - params.room = this.get('room'); - params.instructors = this.get('instructors'); - params.instructorGroups = this.get('instructorGroups'); + this.sendAction('addSingleOffering', params); + } else { + this.sendAction('addMultipleOfferings', params); + } - this.sendAction('addSingleOffering', params); + this.send('cancel'); } else { - this.sendAction('addMultipleOfferings', params); + this.get('flashMessages').alert('general.invalidDatetimes'); } - - this.send('cancel'); }, cancel() { + this.get('flashMessages').clearMessages(); this.sendAction('closeEditor'); } } diff --git a/app/components/offering-manager.js b/app/components/offering-manager.js index 63ca10d6aa..ff1fc9ed8e 100644 --- a/app/components/offering-manager.js +++ b/app/components/offering-manager.js @@ -2,22 +2,20 @@ import Ember from 'ember'; import DS from 'ember-data'; import moment from 'moment'; -const { computed, isEmpty, ObjectProxy, RSVP } = Ember; +const { computed, copy, inject, isEmpty, ObjectProxy, RSVP } = Ember; const { alias, notEmpty } = computed; const { all, Promise } = RSVP; const { PromiseArray } = DS; +const { service } = inject; export default Ember.Component.extend({ - currentUser: Ember.inject.service(), + currentUser: service(), offering: null, isEditing: false, editable: true, sortBy: ['lastName', 'firstName'], classNames: ['offering-manager'], sortedInstructors: Ember.computed.sort('instructors', 'sortBy'), - startTime: null, - endTime: null, - room: null, isMultiDay: false, cohorts: Ember.computed.alias('offering.session.course.cohorts'), availableInstructorGroups: Ember.computed.alias('offering.session.course.school.instructorGroups'), @@ -108,75 +106,120 @@ export default Ember.Component.extend({ }).sortBy('title'); }), + datesValidated() { + const resetStartDate = this.get('buffer.startDate').setHours(0, 0, 0, 0); + const resetEndDate = this.get('buffer.endDate').setHours(0, 0, 0, 0); + + let isEndDateOnOrBeforeStartDate = this.get('buffer.isMultiDay') && resetStartDate >= resetEndDate; + + return isEndDateOnOrBeforeStartDate ? false : true; + }, + + timesValidated() { + const isSingleDay = !this.get('buffer.isMultiDay'); + const startTime = this.get('buffer.startTime'); + const endTime = this.get('buffer.endTime'); + + // Covers edge case where user switches from multi-day to single-day in edit mode + let revisedEndTime = copy(startTime); + revisedEndTime.setHours(endTime.getHours(), endTime.getMinutes(), 0, 0); + + let isEndTimeOnOrBeforeStartTime = isSingleDay && startTime >= revisedEndTime; + + return isEndTimeOnOrBeforeStartTime ? false : true; + }, + + calculateDateTimes() { + let startDate = moment(this.get('buffer.startDate')); + let endDate; + + let starTime = moment(this.get('buffer.startTime')); + startDate.hour(starTime.format('HH')); + startDate.minute(starTime.format('mm')); + + if (this.get('buffer.isMultiDay')){ + endDate = moment(this.get('buffer.endDate')); + } else { + endDate = startDate.clone(); + } + + let endTime = moment(this.get('buffer.endTime')); + endDate.hour(endTime.format('HH')); + endDate.minute(endTime.format('mm')); + + return { startDate, endDate }; + }, + actions: { save() { - var offering = this.get('offering'); - let promises = []; - promises.push(offering.get('instructorGroups').then(instructorGroups => { - let removableInstructorGroups = instructorGroups.filter(group => !this.get('buffer.instructorGroups').contains(group)); - instructorGroups.clear(); - removableInstructorGroups.forEach(group => { - group.get('offerings').removeObject(offering); - }); - this.get('buffer.instructorGroups').forEach(group => { - group.get('offerings').pushObject(offering); - instructorGroups.pushObject(group); - }); - })); - promises.push(offering.get('instructors').then(instructors => { - let removableInstructors = instructors.filter(user => !this.get('buffer.instructors').contains(user)); - instructors.clear(); - removableInstructors.forEach(user => { - user.get('offerings').removeObject(offering); - }); - this.get('buffer.instructors').forEach(user => { - user.get('offerings').pushObject(offering); - instructors.pushObject(user); - }); - })); - promises.push(offering.get('learnerGroups').then(learnerGroups => { - let removeableLearnerGroups = learnerGroups.filter(group => !this.get('buffer.learnerGroups').contains(group)); - learnerGroups.clear(); - removeableLearnerGroups.forEach(group => { - group.get('offerings').removeObject(offering); - }); - this.get('buffer.learnerGroups').forEach(group => { - group.get('offerings').pushObject(offering); - learnerGroups.pushObject(group); + if (this.datesValidated() && this.timesValidated()) { + var offering = this.get('offering'); + let promises = []; + promises.push(offering.get('instructorGroups').then(instructorGroups => { + let removableInstructorGroups = instructorGroups.filter(group => !this.get('buffer.instructorGroups').contains(group)); + instructorGroups.clear(); + removableInstructorGroups.forEach(group => { + group.get('offerings').removeObject(offering); + }); + this.get('buffer.instructorGroups').forEach(group => { + group.get('offerings').pushObject(offering); + instructorGroups.pushObject(group); + }); + })); + promises.push(offering.get('instructors').then(instructors => { + let removableInstructors = instructors.filter(user => !this.get('buffer.instructors').contains(user)); + instructors.clear(); + removableInstructors.forEach(user => { + user.get('offerings').removeObject(offering); + }); + this.get('buffer.instructors').forEach(user => { + user.get('offerings').pushObject(offering); + instructors.pushObject(user); + }); + })); + promises.push(offering.get('learnerGroups').then(learnerGroups => { + let removeableLearnerGroups = learnerGroups.filter(group => !this.get('buffer.learnerGroups').contains(group)); + learnerGroups.clear(); + removeableLearnerGroups.forEach(group => { + group.get('offerings').removeObject(offering); + }); + this.get('buffer.learnerGroups').forEach(group => { + group.get('offerings').pushObject(offering); + learnerGroups.pushObject(group); + }); + })); + + let datesHash = this.calculateDateTimes(); + + const room = this.get('buffer.room'); + const startDate = datesHash.startDate.toDate(); + const endDate = datesHash.endDate.toDate(); + + offering.setProperties({ room, startDate, endDate }); + + promises.pushObject(offering.save()); + Ember.RSVP.all(promises).then(() => { + if(!this.get('isDestroyed')){ + this.sendAction('save', offering); + this.send('cancel'); + } }); - })); - let startDate = moment(this.get('buffer.startDate')); - let endDate; - if(this.get('buffer.isMultiDay')){ - endDate = moment(this.get('buffer.endDate')); } else { - endDate = startDate.clone(); - let endTime = moment(this.get('buffer.endDate')); - endDate.hour(endTime.format('HH')); - endDate.minute(endTime.format('mm')); + this.get('flashMessages').alert('general.invalidDatetimes'); } - - offering.set('room', this.get('buffer.room')); - offering.set('startDate', startDate.toDate()); - offering.set('endDate', endDate.toDate()); - promises.pushObject(offering.save()); - Ember.RSVP.all(promises).then(() => { - if(!this.get('isDestroyed')){ - this.set('isEditing', false); - this.set('buffer', null); - this.sendAction('save', offering); - } - }); }, - edit: function(){ + edit() { let offering = this.get('offering'); - if(offering){ - let buffer = Ember.Object.create({ - startDate: moment(offering.get('startDate')).toDate(), - endDate: moment(offering.get('endDate')).toDate(), - room: offering.get('room'), - isMultiDay: offering.get('isMultiDay') - }); + + if (offering) { + const startDate = offering.get('startDate'); + const endDate = offering.get('endDate'); + const startTime = copy(startDate); + const endTime = copy(endDate); + const room = offering.get('room'); + const isMultiDay = offering.get('isMultiDay'); + + let buffer = Ember.Object.create({ startDate, endDate, startTime, endTime, room, isMultiDay }); let collections = [ 'instructors', @@ -199,6 +242,7 @@ export default Ember.Component.extend({ cancel: function(){ this.set('buffer', null); this.set('isEditing', false); + this.get('flashMessages').clearMessages(); }, addInstructorGroupToBuffer(instructorGroup){ this.get('buffer.instructorGroups').pushObject(instructorGroup); @@ -224,19 +268,23 @@ export default Ember.Component.extend({ confirmRemove: function(){ this.set('showRemoveConfirmation', true); }, - changeEndTime(date){ - let newEnd = moment(date); - let endDate = moment(this.get('buffer.endDate')); - endDate.hour(newEnd.format('HH')); - endDate.minute(newEnd.format('mm')); - this.set('buffer.endDate', endDate.toDate()); - }, - changeStartTime(date){ + + changeStartTime(date) { let newStart = moment(date); - let startDate = moment(this.get('buffer.startDate')); - startDate.hour(newStart.format('HH')); - startDate.minute(newStart.format('mm')); - this.set('buffer.startDate', startDate.toDate()); + let startTime = moment(this.get('buffer.startTime')); + + startTime.hour(newStart.format('HH')); + startTime.minute(newStart.format('mm')); + this.set('buffer.startTime', startTime.toDate()); + }, + + changeEndTime(date) { + let newEnd = moment(date); + let endTime = moment(this.get('buffer.endTime')); + + endTime.hour(newEnd.format('HH')); + endTime.minute(newEnd.format('mm')); + this.set('buffer.endTime', endTime.toDate()); }, addLearnerGroup(group) { diff --git a/app/components/time-picker.js b/app/components/time-picker.js index 095bef94c6..3d358b16b0 100644 --- a/app/components/time-picker.js +++ b/app/components/time-picker.js @@ -9,17 +9,7 @@ export default Ember.Component.extend({ classNames: ['time-picker'], date: null, dateBuffer: oneWay('date'), - - hour: computed('dateBuffer', function() { - const defaultHour = this.get('defaultHour'); - - if (defaultHour) { - return defaultHour; - } else { - return momentFormat('dateBuffer', 'h'); - } - }), - + hour: momentFormat('dateBuffer', 'h'), minute: momentFormat('dateBuffer', 'mm'), ampm: momentFormat('dateBuffer', 'a'), hours: ['1','2','3','4','5','6','7','8','9','10','11','12'], @@ -27,8 +17,6 @@ export default Ember.Component.extend({ ampms: ['am', 'pm'], actions: { changeHour(){ - this.set('defaultHour', null); - let select = this.$('select')[0]; let selectedIndex = select.selectedIndex; let hours = this.get('hours'); diff --git a/app/locales/en/translations.js b/app/locales/en/translations.js index 54bf2efae9..f2878afa7d 100644 --- a/app/locales/en/translations.js +++ b/app/locales/en/translations.js @@ -91,7 +91,8 @@ export default { 'notFoundMessage': "Rats! I couldn't find that. Please check your page address, and try again.", 'myCourses': 'My Courses', 'waitSaving': 'saving... one moment...', - 'select': 'Select' + 'select': 'Select', + 'invalidDatetimes': 'Invalid dates/times', }, 'programs': { 'programTitle': 'Program Title', @@ -240,6 +241,8 @@ export default { 'calendarOn': 'Calendar On', 'calendarOff': 'Calendar Off', 'confirmRemove': 'Are you sure you want to delete this offering with {{learnerGroupCount}} learner groups? This action cannot be undone.', + 'offering': 'Offering', + 'smallGroups': 'Small Groups' }, 'learningMaterials': { 'displayName': 'Display Name', diff --git a/app/locales/es/translations.js b/app/locales/es/translations.js index a4caf385c8..51ddf12c84 100644 --- a/app/locales/es/translations.js +++ b/app/locales/es/translations.js @@ -91,7 +91,8 @@ export default { 'notFoundMessage': "Malditas! Éso no puede encontrar. Favor de verificar la dirección de la página, y trata otra vez.", 'myCourses': 'Mis Cursos', 'waitSaving': 'Salvando... un momento...', - 'select': 'Selecciona' + 'select': 'Selecciona', + 'invalidDatetimes': 'Fecha/Hora Inválida' }, 'programs': { 'programTitle': 'Titulo de Programa', @@ -240,6 +241,8 @@ export default { 'calendarOn': 'Calendario en Uso', 'calendarOff': 'Calendario Apagado', 'confirmRemove': '¿Estás seguro que quieres borrar este ofrecimiento con {{learnerGroupCount}} grupos de aprendedores? Esta acción no se puede deshacer.', + 'offering': 'Ofrecimiento', + 'smallGroups': 'Grupos Pequeños' }, 'learningMaterials': { 'displayName': 'Nombre de Mostrar', diff --git a/app/locales/fr/translations.js b/app/locales/fr/translations.js index adbdfb78f4..f61d13e9f7 100755 --- a/app/locales/fr/translations.js +++ b/app/locales/fr/translations.js @@ -91,7 +91,8 @@ export default { 'notFoundMessage': "Zut! Je ne pouvais pas trouver cette chôse. S'il vous plaît vérifier votre adresse de la page et essayez à nouveau.", 'myCourses': "Mes Cours", 'waitSaving': "Travaille dessus... un moment...", - 'select': 'Sélectionnez' + 'select': 'Sélectionnez', + 'invalidDatetimes': 'Dates/Heures invalides', }, 'programs': { 'programTitle': "Titre de Diplôme", @@ -240,6 +241,8 @@ export default { 'calendarOn': "Calendrier allumé", 'calendarOff': "Calendrier éteindé", 'confirmRemove': "Êtes-vous sûr vous voulez supprimer cette offre avec {{learnerGroupCount}} groupes d'apprenants? Ça action ne peut pas être défait.", + 'offering': 'Offre', + 'smallGroups': 'Groupes particulaires' }, 'learningMaterials': { 'displayName': "Nom d'affichage", diff --git a/app/styles/components/_click-choice-buttons.scss b/app/styles/components/_click-choice-buttons.scss index 00bf26f551..95e7728d9a 100644 --- a/app/styles/components/_click-choice-buttons.scss +++ b/app/styles/components/_click-choice-buttons.scss @@ -10,7 +10,7 @@ $very-dark-blue: #193047; font-weight: bold; height: 30px; outline: none; - width: 120px; + width: 140px; } @mixin active-button { diff --git a/app/styles/components/_flashMessages.scss b/app/styles/components/_flashMessages.scss index 22b899961f..08c80b30ac 100644 --- a/app/styles/components/_flashMessages.scss +++ b/app/styles/components/_flashMessages.scss @@ -4,7 +4,7 @@ @mixin flash($color) { background-color: $color; - color: darken($color, 25%); + color: $white; display: block; opacity: .75; padding: $base-font-size / 2; diff --git a/app/templates/components/offering-editor.hbs b/app/templates/components/offering-editor.hbs index 337df7beed..e5678e0d8b 100644 --- a/app/templates/components/offering-editor.hbs +++ b/app/templates/components/offering-editor.hbs @@ -2,8 +2,8 @@