diff --git a/SAS/TMSS/frontend/tmss_webapp/src/App.js b/SAS/TMSS/frontend/tmss_webapp/src/App.js index ca7fab811ebe467a571c35f7a475e4678d545c07..3d4e9e0ac8152ae0a427918bf569f5668d1e0142 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/App.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/App.js @@ -196,13 +196,13 @@ class App extends Component { //window.removeEventListener('popstate', this.onBackButtonEvent); } - close = () => { + close = () => { this.setState({showDirtyDialog: false}); } /** * Cancel edit and redirect to Cycle View page */ - cancelEdit = () => { + cancelEdit = () => { this.setState({ isEditDirty: false, showDirtyDialog: false }); this.state.toPathCallback(); } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/reservation.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/reservation.scss index 7372a5dcf271f473bdeb19f7c7a9a96b6f115fa3..5442a492fbfe35e2c694632c1caaa6aa98b931b0 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/reservation.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/reservation.scss @@ -9,4 +9,9 @@ position: relative; top: 2.2em; width: 40em; +} + +.p-field.p-grid, .p-formgrid.p-grid { + margin-left: -2px; + margin-top: 0; } \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.create.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.create.js index 5ed4ceff11c1bb985567f5e2159c49213b16e065..2b657133c940fa7dc3fa80f3178685ff8dd0b9d7 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.create.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.create.js @@ -5,7 +5,7 @@ import { publish } from '../../App'; import moment from 'moment'; import { Growl } from 'primereact/components/growl/Growl'; import { Dropdown } from 'primereact/dropdown'; -import {InputText } from 'primereact/inputtext'; +import { InputText } from 'primereact/inputtext'; import { InputTextarea } from 'primereact/inputtextarea'; import { Button } from 'primereact/button'; import { Dialog } from 'primereact/components/dialog/Dialog'; @@ -14,11 +14,13 @@ import AppLoader from '../../layout/components/AppLoader'; import PageHeader from '../../layout/components/PageHeader'; import UIConstants from '../../utils/ui.constants'; import { CustomDialog } from '../../layout/components/CustomDialog'; +import { InputMask } from 'primereact/inputmask'; import ProjectService from '../../services/project.service'; import ReservationService from '../../services/reservation.service'; import Jeditor from '../../components/JSONEditor/JEditor'; import UtilService from '../../services/util.service'; +import UnitConverter from '../../utils/unit.converter'; import "flatpickr/dist/flatpickr.css"; @@ -28,22 +30,23 @@ import "flatpickr/dist/flatpickr.css"; export class ReservationCreate extends Component { constructor(props) { super(props); - this.state= { + this.state = { showDialog: false, isDirty: false, isLoading: true, - redirect: null, + redirect: null, paramsSchema: null, // JSON Schema to be generated from strategy template to pass to JSON editor - dialog: { header: '', detail: ''}, // Dialog properties + dialog: { header: '', detail: '' }, // Dialog properties touched: { name: '', }, - reservation: { + reservation: { name: '', - description: '', + description: '', start_time: null, stop_time: null, - project: (props.match?props.match.params.project:null) || null, + duration: null, + project: (props.match ? props.match.params.project : null) || null, }, reservationStrategy: { id: null, @@ -59,10 +62,10 @@ export class ReservationCreate extends Component { // Validateion Rules this.formRules = { - name: {required: true, message: "Name can not be empty"}, - description: {required: true, message: "Description can not be empty"}, - // project: {required: true, message: "Project can not be empty"}, - start_time: {required: true, message: "Start Time can not be empty"}, + name: { required: true, message: "Name can not be empty" }, + description: { required: true, message: "Description can not be empty" }, + // project: {required: true, message: "Project can not be empty"}, + start_time: { required: true, message: "Start Time can not be empty" }, }; this.tooltipOptions = UIConstants.tooltipOptions; this.setEditorOutput = this.setEditorOutput.bind(this); @@ -74,22 +77,23 @@ export class ReservationCreate extends Component { this.initReservation = this.initReservation.bind(this); this.changeStrategy = this.changeStrategy.bind(this); this.setEditorFunction = this.setEditorFunction.bind(this); + this.isValidDuration = this.isValidDuration.bind(this); } async componentDidMount() { await this.initReservation(); } - + /** * Initialize the reservation and relevant details */ async initReservation() { - const promises = [ ProjectService.getProjectList(), - ReservationService.getReservationTemplates(), - UtilService.getUTC(), - ReservationService.getReservationStrategyTemplates() - ]; - let emptyProjects = [{url: null, name: "Select Project"}]; + const promises = [ProjectService.getProjectList(), + ReservationService.getReservationTemplates(), + UtilService.getUTC(), + ReservationService.getReservationStrategyTemplates() + ]; + let emptyProjects = [{ url: null, name: "Select Project" }]; Promise.all(promises).then(responses => { this.projects = emptyProjects.concat(responses[0]); this.reservationTemplates = responses[1]; @@ -99,7 +103,7 @@ export class ReservationCreate extends Component { let schema = { properties: {} }; - if(reservationTemplate) { + if (reservationTemplate) { schema = reservationTemplate.schema; } this.setState({ @@ -108,30 +112,31 @@ export class ReservationCreate extends Component { reservationTemplate: reservationTemplate, systemTime: systemTime, }); - }); - + }); + } - + /** * * @param {Id} strategyId - id value of reservation strategy template */ async changeStrategy(strategyId) { - this.setState({isLoading: true}); - const reservationStrategy = _.find(this.reservationStrategies, {'id': strategyId}); + this.setState({ isLoading: true }); + const reservationStrategy = _.find(this.reservationStrategies, { 'id': strategyId }); let paramsOutput = {}; - if(reservationStrategy.template.parameters) { + if (reservationStrategy.template.parameters) { //if reservation strategy has parameter then prepare output parameter - } else { + } else { paramsOutput = _.cloneDeep(reservationStrategy.template); delete paramsOutput["$id"]; } - this.setState({ - isLoading: false, - reservationStrategy: reservationStrategy, - paramsOutput: paramsOutput, - isDirty: true}); + this.setState({ + isLoading: false, + reservationStrategy: reservationStrategy, + paramsOutput: paramsOutput, + isDirty: true + }); this.initReservation(); } @@ -143,23 +148,27 @@ export class ReservationCreate extends Component { setReservationParams(key, value) { let reservation = _.cloneDeep(this.state.reservation); reservation[key] = value; - if ( !this.state.isDirty && !_.isEqual(this.state.reservation, reservation) ) { - this.setState({reservation: reservation, validForm: this.validateForm(key), validEditor: this.validateEditor(), touched: { - ...this.state.touched, - [key]: true - }, isDirty: true}); + if (!this.state.isDirty && !_.isEqual(this.state.reservation, reservation)) { + this.setState({ + reservation: reservation, validForm: this.validateForm(key), validEditor: this.validateEditor(), touched: { + ...this.state.touched, + [key]: true + }, isDirty: true + }); publish('edit-dirty', true); - } else { - this.setState({reservation: reservation, validForm: this.validateForm(key), validEditor: this.validateEditor(),touched: { - ...this.state.touched, - [key]: true - }}); + } else { + this.setState({ + reservation: reservation, validForm: this.validateForm(key), validEditor: this.validateEditor(), touched: { + ...this.state.touched, + [key]: true + } + }); } - } + } - /** - * This function is mainly added for Unit Tests. If this function is removed Unit Tests will fail. - */ + /** + * This function is mainly added for Unit Tests. If this function is removed Unit Tests will fail. + */ validateEditor() { return this.validEditor; } @@ -171,20 +180,23 @@ export class ReservationCreate extends Component { */ setParams(key, value, type) { let reservation = this.state.reservation; - switch(type) { + switch (type) { case 'NUMBER': { - reservation[key] = value?parseInt(value):0; + reservation[key] = value ? parseInt(value) : 0; break; } default: { - reservation[key] = value; + reservation[key] = value; break; } } - this.setState({reservation: reservation, validForm: this.validateForm(key), isDirty: true}); + this.setState({ reservation: reservation, validForm: this.validateForm(key), isDirty: true }, + () => { + this.setDurationOrEndValue(key); + }); publish('edit-dirty', true); } - + /** * Validation function to validate the form or field based on the form rules. * If no argument passed for fieldName, validates all fields in the form. @@ -202,13 +214,13 @@ export class ReservationCreate extends Component { const fieldValue = this.state.reservation[fieldName]; if (rule.required) { if (!fieldValue) { - errors[fieldName] = rule.message?rule.message:`${fieldName} is required`; - } else { + errors[fieldName] = rule.message ? rule.message : `${fieldName} is required`; + } else { validFields[fieldName] = true; } } - } - } else { + } + } else { errors = {}; validFields = {}; for (const fieldName in this.formRules) { @@ -216,14 +228,14 @@ export class ReservationCreate extends Component { const fieldValue = this.state.reservation[fieldName]; if (rule.required) { if (!fieldValue) { - errors[fieldName] = rule.message?rule.message:`${fieldName} is required`; - } else { + errors[fieldName] = rule.message ? rule.message : `${fieldName} is required`; + } else { validFields[fieldName] = true; } } } } - this.setState({errors: errors, validFields: validFields}); + this.setState({ errors: errors, validFields: validFields }); if (Object.keys(validFields).length === Object.keys(this.formRules).length) { validForm = true; delete errors['start_time']; @@ -239,7 +251,20 @@ export class ReservationCreate extends Component { errors['stop_time'] = "End Time cannot be same or before Start Time"; delete errors['start_time']; } - this.setState({errors: errors}); + this.setState({ errors: errors }); + } + if (fieldName === 'duration' && this.state.reservation.duration) { + var values = this.state.reservation.duration.split(' '); + var days = values[0]; + var dValues = values[1].split(':'); + delete errors['duration']; + if ((days *1 )===0 && (dValues[0] * 1) === 0 && (dValues[1] * 1) === 0 && (dValues[2] * 1) === 0) { + validForm = false; + if (!fieldName || fieldName === 'duration') { + errors['duration'] = "Duration cannot be zero"; + } + this.setState({ errors: errors }); + } } return validForm; } @@ -260,31 +285,35 @@ export class ReservationCreate extends Component { setEditorOutput(jsonOutput, errors) { this.paramsOutput = jsonOutput; this.validEditor = errors.length === 0; - if ( !this.state.isDirty && this.state.paramsOutput && !_.isEqual(this.state.paramsOutput, jsonOutput) ) { - this.setState({ paramsOutput: jsonOutput, + if (!this.state.isDirty && this.state.paramsOutput && !_.isEqual(this.state.paramsOutput, jsonOutput)) { + this.setState({ + paramsOutput: jsonOutput, validEditor: errors.length === 0, validForm: this.validateForm(), - isDirty: true}); - publish('edit-dirty', true); - } else { - this.setState({ paramsOutput: jsonOutput, + isDirty: true + }); + publish('edit-dirty', true); + } else { + this.setState({ + paramsOutput: jsonOutput, validEditor: errors.length === 0, - validForm: this.validateForm()}); + validForm: this.validateForm() + }); } } - async saveReservation(){ + async saveReservation() { let reservation = this.state.reservation; let project = this.projects.find(project => project.name === reservation.project); reservation['start_time'] = moment(reservation['start_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT); - reservation['stop_time'] = reservation['stop_time']?moment(reservation['stop_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT):null; - reservation['project']= project ? project.url: null; - reservation['specifications_template']= this.reservationTemplates[0].url; - reservation['specifications_doc']= this.paramsOutput; - reservation = await ReservationService.saveReservation(reservation); - if (reservation && reservation.id){ - const dialog = {header: 'Success', detail: 'Reservation is created successfully. Do you want to create another Reservation?'}; - this.setState({ dialogVisible: true, dialog: dialog, paramsOutput: {}, showDialog: false, isDirty: false}) + reservation['stop_time'] = reservation['stop_time'] ? moment(reservation['stop_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT) : null; + reservation['project'] = project ? project.url : null; + reservation['specifications_template'] = this.reservationTemplates[0].url; + reservation['specifications_doc'] = this.paramsOutput; + reservation = await ReservationService.saveReservation(reservation); + if (reservation && reservation.id) { + const dialog = { header: 'Success', detail: 'Reservation is created successfully. Do you want to create another Reservation?' }; + this.setState({ dialogVisible: true, dialog: dialog, paramsOutput: {}, showDialog: false, isDirty: false }) publish('edit-dirty', false); }/* else { this.growl.show({severity: 'error', summary: 'Error Occured', detail: 'Unable to save Reservation', showDialog: false, isDirty: false}); @@ -295,40 +324,40 @@ export class ReservationCreate extends Component { * Reset function to be called when user wants to create new Reservation */ reset() { - let tmpReservation= { + let tmpReservation = { name: '', - description: '', + description: '', start_time: '', stop_time: '', project: '', } this.setState({ dialogVisible: false, - dialog: { header: '', detail: ''}, + dialog: { header: '', detail: '' }, errors: [], reservation: tmpReservation, reservationStrategy: { id: null, }, - paramsSchema: null, + paramsSchema: null, paramsOutput: null, validEditor: false, validFields: {}, - touched:false, + touched: false, stationGroup: [], - showDialog: false, + showDialog: false, isDirty: false }); this.initReservation(); } - /** - * Cancel Reservation creation and redirect - */ - cancelCreate() { + /** + * Cancel Reservation creation and redirect + */ + cancelCreate() { publish('edit-dirty', false); this.props.history.goBack(); - this.setState({showDialog: false}); + this.setState({ showDialog: false }); this.props.history.goBack(); } @@ -336,15 +365,15 @@ export class ReservationCreate extends Component { * warn before cancel the page if any changes detected */ checkIsDirty() { - if( this.state.isDirty ){ - this.setState({showDialog: true}); + if (this.state.isDirty) { + this.setState({ showDialog: true }); } else { this.cancelCreate(); } } - + close() { - this.setState({showDialog: false}); + this.setState({ showDialog: false }); } /** @@ -352,121 +381,130 @@ export class ReservationCreate extends Component { * @param {Function} editorFunction */ setEditorFunction(editorFunction) { - this.setState({editorFunction: editorFunction}); + this.setState({ editorFunction: editorFunction }); } - + + /** + * Function to set the value for the dependant fields when value is set to one field. + * When start_time or stop_time is changed, duration will be updated accordingly. + * Similarly if duration is changed, stop_time is updated. + * @param {String} key - property name of the reservation + */ + setDurationOrEndValue = (key) => { + let state = this.state; + if (key === 'start_time' || key === 'stop_time') { + if (this.state.reservation.start_time && this.state.reservation.stop_time) { + var delta = Math.abs(this.state.reservation.start_time - this.state.reservation.stop_time) / 1000; + let tempDuration = UnitConverter.getSecsToDDHHmmss(delta); + this.setDurationOrStopTime('duration', tempDuration); + } else if (key === 'start_time' && this.state.reservation.start_time && this.state.reservation.duration) { + let stopDate = UnitConverter.getEndDateFromDuration(this.state.reservation.start_time, this.state.reservation.duration); + this.setDurationOrStopTime('stop_time', stopDate); + } else if (key === 'stop_time' && !this.state.reservation.stop_time) { + this.setDurationOrStopTime('duration', ""); + } + } + else if (key === 'duration') { + if (this.state.reservation.start_time) { + let stopDate = UnitConverter.getEndDateFromDuration(this.state.reservation.start_time, this.state.reservation.duration); + this.setDurationOrStopTime('stop_time', stopDate); + } + } + } + + /** + * Function to set calcualted value for either duration or stop_time + * @param {String} key - name of the field + * @param {*} value - value to set for the field + */ + setDurationOrStopTime(key, value) { + let reservation = this.state.reservation; + reservation[key] = value; + this.setState({ reservation: reservation, validForm: this.validateForm(key), isDirty: true }); + } + + /** + * Function to validate the duration field. + * @param {String} value - Duration in format 'Days HH:mm:ss' + * @returns boolean + */ + isValidDuration(value) { + let errors = this.state.errors; + let touched = this.state.touched; + let reservation = this.state.reservation; + let validForm = this.state.validForm; + if (value.length === 12 && (value === "000 00:00:00" || + !value.match(/^([0-1]90 00:00:00)|([0-1][0-8][0-9] ([0-1]?\d|2[0-3]):([0-5]?\d):([0-5]?\d))$/))) { + errors.duration = "Not valid duration. Duration should be in Days Hours:minutes:seconds. Min - 000 00:00:01, Max - 190 00:00:00"; + touched.duration = true; + validForm = false; + } else { + delete errors["duration"]; + delete touched["duration"]; + validForm = this.validateForm(); + } + reservation.duration = value; + this.setState({errors: errors, touched: touched, reservation: reservation, validForm: validForm}); + return errors.duration?false:true; + } + render() { if (this.state.redirect) { - return <Redirect to={ {pathname: this.state.redirect} }></Redirect> + return <Redirect to={{ pathname: this.state.redirect }}></Redirect> } const schema = this.state.paramsSchema; - let jeditor = null; if (schema) { if (this.state.reservation.specifications_doc) { delete this.state.reservation.specifications_doc.$id; delete this.state.reservation.specifications_doc.$schema; } - jeditor = React.createElement(Jeditor, {title: "Reservation Parameters", - schema: schema, - initValue: this.state.paramsOutput, - callback: this.setEditorOutput, - parentFunction: this.setEditorFunction - }); + jeditor = React.createElement(Jeditor, { + title: "Reservation Parameters", + schema: schema, + initValue: this.state.paramsOutput, + callback: this.setEditorOutput, + parentFunction: this.setEditorFunction + }); } return ( <React.Fragment> <Growl ref={(el) => this.growl = el} /> - <PageHeader location={this.props.location} title={'Reservation - Add'} - actions={[{icon: 'fa-window-close' ,title:'Click to close Reservation creation', - type: 'button', actOn: 'click', props:{ callback: this.checkIsDirty }}]}/> + <PageHeader location={this.props.location} title={'Reservation - Add'} + actions={[{ + icon: 'fa-window-close', title: 'Click to close Reservation creation', + type: 'button', actOn: 'click', props: { callback: this.checkIsDirty } + }]} /> { this.state.isLoading ? <AppLoader /> : - <> - <div> - <div className="p-fluid"> - <div className="p-field p-grid"> - <label htmlFor="reservationname" className="col-lg-2 col-md-2 col-sm-12">Name <span style={{color:'red'}}>*</span></label> - <div className="col-lg-3 col-md-3 col-sm-12"> - <InputText className={(this.state.errors.name && this.state.touched.name) ?'input-error':''} id="reservationname" data-testid="name" - tooltip="Enter name of the Reservation Name" tooltipOptions={this.tooltipOptions} maxLength="128" - ref={input => {this.nameInput = input;}} - value={this.state.reservation.name} autoFocus - onChange={(e) => this.setReservationParams('name', e.target.value)} - onBlur={(e) => this.setReservationParams('name', e.target.value)}/> - <label className={(this.state.errors.name && this.state.touched.name)?"error":"info"}> - {this.state.errors.name && this.state.touched.name ? this.state.errors.name : "Max 128 characters"} - </label> - </div> - <div className="col-lg-1 col-md-1 col-sm-12"></div> - <label htmlFor="description" className="col-lg-2 col-md-2 col-sm-12">Description <span style={{color:'red'}}>*</span></label> - <div className="col-lg-3 col-md-3 col-sm-12"> - <InputTextarea className={(this.state.errors.description && this.state.touched.description) ?'input-error':''} rows={3} cols={30} - tooltip="Longer description of the Reservation" - tooltipOptions={this.tooltipOptions} - maxLength="128" - data-testid="description" - value={this.state.reservation.description} - onChange={(e) => this.setReservationParams('description', e.target.value)} - onBlur={(e) => this.setReservationParams('description', e.target.value)}/> - <label className={(this.state.errors.description && this.state.touched.description) ?"error":"info"}> - {(this.state.errors.description && this.state.touched.description) ? this.state.errors.description : "Max 255 characters"} - </label> - </div> - </div> - <div className="p-field p-grid"> - <label className="col-lg-2 col-md-2 col-sm-12">Start Time <span style={{color:'red'}}>*</span></label> + <> + <div> + <div className="p-fluid"> + <div className="p-field p-grid"> + <label htmlFor="reservationname" className="col-lg-2 col-md-2 col-sm-12">Name <span style={{ color: 'red' }}>*</span></label> <div className="col-lg-3 col-md-3 col-sm-12"> - <Flatpickr data-enable-time data-input options={{ - "inlineHideInput": true, - "wrap": true, - "enableSeconds": true, - "time_24hr": true, - "minuteIncrement": 1, - "allowInput": true, - "defaultDate": this.state.systemTime.format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT), - "defaultHour": this.state.systemTime.hours(), - "defaultMinute": this.state.systemTime.minutes() - }} - title="Start of this reservation" - value={this.state.reservation.start_time} - onChange= {value => {this.setParams('start_time', value[0]?value[0]:this.state.reservation.start_time); - this.setReservationParams('start_time', value[0]?value[0]:this.state.reservation.start_time)}} > - <input type="text" data-input className={`p-inputtext p-component ${this.state.errors.start_time && this.state.touched.start_time?'input-error':''}`} /> - <i className="fa fa-calendar" data-toggle style={{position: "absolute", marginLeft: '-25px', marginTop:'5px', cursor: 'pointer'}} ></i> - <i className="fa fa-times" style={{position: "absolute", marginLeft: '-50px', marginTop:'5px', cursor: 'pointer'}} - onClick={e => {this.setParams('start_time', ''); this.setReservationParams('start_time', '')}}></i> - </Flatpickr> - <label className={this.state.errors.start_time && this.state.touched.start_time?"error":"info"}> - {this.state.errors.start_time && this.state.touched.start_time ? this.state.errors.start_time : ""} + <InputText className={(this.state.errors.name && this.state.touched.name) ? 'input-error' : ''} id="reservationname" data-testid="name" + tooltip="Enter name of the Reservation Name" tooltipOptions={this.tooltipOptions} maxLength="128" + ref={input => { this.nameInput = input; }} + value={this.state.reservation.name} autoFocus + onChange={(e) => this.setReservationParams('name', e.target.value)} + onBlur={(e) => this.setReservationParams('name', e.target.value)} /> + <label className={(this.state.errors.name && this.state.touched.name) ? "error" : "info"}> + {this.state.errors.name && this.state.touched.name ? this.state.errors.name : "Max 128 characters"} </label> </div> <div className="col-lg-1 col-md-1 col-sm-12"></div> - - <label className="col-lg-2 col-md-2 col-sm-12">End Time</label> + <label htmlFor="description" className="col-lg-2 col-md-2 col-sm-12">Description <span style={{ color: 'red' }}>*</span></label> <div className="col-lg-3 col-md-3 col-sm-12"> - <Flatpickr data-enable-time data-input options={{ - "inlineHideInput": true, - "wrap": true, - "enableSeconds": true, - "time_24hr": true, - "minuteIncrement": 1, - "allowInput": true, - "minDate": this.state.reservation.start_time?this.state.reservation.start_time.toDate:'', - "defaultDate": this.state.systemTime.format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT), - "defaultHour": this.state.systemTime.hours(), - "defaultMinute": this.state.systemTime.minutes() - }} - title="End of this reservation. If empty, then this reservation is indefinite." - value={this.state.reservation.stop_time} - onChange= {value => {this.setParams('stop_time', value[0]?value[0]:this.state.reservation.stop_time); - this.setReservationParams('stop_time', value[0]?value[0]:this.state.reservation.stop_time)}} > - <input type="text" data-input className={`p-inputtext p-component ${this.state.errors.stop_time && this.state.touched.stop_time?'input-error':''}`} /> - <i className="fa fa-calendar" data-toggle style={{position: "absolute", marginLeft: '-25px', marginTop:'5px', cursor: 'pointer'}} ></i> - <i className="fa fa-times" style={{position: "absolute", marginLeft: '-50px', marginTop:'5px', cursor: 'pointer'}} - onClick={e => {this.setParams('stop_time', ''); this.setReservationParams('stop_time', '')}}></i> - </Flatpickr> - <label className={this.state.errors.stop_time && this.state.touched.stop_time?"error":"info"}> - {this.state.errors.stop_time && this.state.touched.stop_time ? this.state.errors.stop_time : ""} + <InputTextarea className={(this.state.errors.description && this.state.touched.description) ? 'input-error' : ''} rows={3} cols={30} + tooltip="Longer description of the Reservation" + tooltipOptions={this.tooltipOptions} + maxLength="128" + data-testid="description" + value={this.state.reservation.description} + onChange={(e) => this.setReservationParams('description', e.target.value)} + onBlur={(e) => this.setReservationParams('description', e.target.value)} /> + <label className={(this.state.errors.description && this.state.touched.description) ? "error" : "info"}> + {(this.state.errors.description && this.state.touched.description) ? this.state.errors.description : "Max 255 characters"} </label> </div> </div> @@ -474,74 +512,160 @@ export class ReservationCreate extends Component { <div className="p-field p-grid"> <label htmlFor="project" className="col-lg-2 col-md-2 col-sm-12">Project</label> <div className="col-lg-3 col-md-3 col-sm-12" data-testid="project" > - <Dropdown inputId="project" optionLabel="name" optionValue="name" - tooltip="Project" tooltipOptions={this.tooltipOptions} - value={this.state.reservation.project} - options={this.projects} - onChange={(e) => {this.setParams('project',e.value)}} - placeholder="Select Project" /> - <label className={(this.state.errors.project && this.state.touched.project) ?"error":"info"}> + <Dropdown inputId="project" optionLabel="name" optionValue="name" + tooltip="Project" tooltipOptions={this.tooltipOptions} + value={this.state.reservation.project} + options={this.projects} + onChange={(e) => { this.setParams('project', e.value) }} + placeholder="Select Project" /> + <label className={(this.state.errors.project && this.state.touched.project) ? "error" : "info"}> {(this.state.errors.project && this.state.touched.project) ? this.state.errors.project : "Select Project"} </label> </div> <div className="col-lg-1 col-md-1 col-sm-12"></div> <label htmlFor="strategy" className="col-lg-2 col-md-2 col-sm-12">Reservation Strategy</label> <div className="col-lg-3 col-md-3 col-sm-12" data-testid="strategy" > - <Dropdown inputId="strategy" optionLabel="name" optionValue="id" - tooltip="Choose Reservation Strategy Template to set default values for create Reservation" tooltipOptions={this.tooltipOptions} - value={this.state.reservationStrategy.id} - options={this.reservationStrategies} - onChange={(e) => {this.changeStrategy(e.value)}} - placeholder="Select Strategy" /> - <label className={(this.state.errors.reservationStrategy && this.state.touched.reservationStrategy) ?"error":"info"}> + <Dropdown inputId="strategy" optionLabel="name" optionValue="id" + tooltip="Choose Reservation Strategy Template to set default values for create Reservation" tooltipOptions={this.tooltipOptions} + value={this.state.reservationStrategy.id} + options={this.reservationStrategies} + onChange={(e) => { this.changeStrategy(e.value) }} + placeholder="Select Strategy" /> + <label className={(this.state.errors.reservationStrategy && this.state.touched.reservationStrategy) ? "error" : "info"}> {(this.state.errors.reservationStrategy && this.state.touched.reservationStrategy) ? this.state.errors.reservationStrategy : "Select Reservation Strategy Template"} </label> </div> + + <div className="p-field p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Start Time <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <Flatpickr data-enable-time data-input options={{ + "inlineHideInput": true, + "wrap": true, + "enableSeconds": true, + "time_24hr": true, + "minuteIncrement": 1, + "allowInput": true, + "defaultDate": this.state.systemTime.format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT), + "defaultHour": this.state.systemTime.hours(), + "defaultMinute": this.state.systemTime.minutes() + }} + title="Start of this reservation" + value={this.state.reservation.start_time} + onChange={value => { + this.setParams('start_time', value[0] ? value[0] : this.state.reservation.start_time); + this.setReservationParams('start_time', value[0] ? value[0] : this.state.reservation.start_time) + }} > + <input type="text" data-input className={`p-inputtext p-component ${this.state.errors.start_time && this.state.touched.start_time ? 'input-error' : ''}`} /> + <i className="fa fa-calendar" data-toggle style={{ position: "absolute", marginLeft: '-25px', marginTop: '5px', cursor: 'pointer' }} ></i> + <i className="fa fa-times" style={{ position: "absolute", marginLeft: '-50px', marginTop: '5px', cursor: 'pointer' }} + onClick={e => { this.setParams('start_time', ''); this.setReservationParams('start_time', '') }}></i> + </Flatpickr> + <label className={this.state.errors.start_time && this.state.touched.start_time ? "error" : "info"}> + {this.state.errors.start_time && this.state.touched.start_time ? this.state.errors.start_time : ""} + </label> + </div> + <div className="col-lg-1 col-md-1 col-sm-12"></div> + <label className="col-lg-2 col-md-2 col-sm-12">End Time</label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <Flatpickr data-enable-time data-input options={{ + "inlineHideInput": true, + "wrap": true, + "enableSeconds": true, + "time_24hr": true, + "minuteIncrement": 1, + "allowInput": true, + "minDate": this.state.reservation.start_time ? this.state.reservation.start_time.toDate : '', + "defaultDate": this.state.systemTime.format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT), + "defaultHour": this.state.systemTime.hours(), + "defaultMinute": this.state.systemTime.minutes() + }} + title="End of this reservation. If empty, then this reservation is indefinite." + value={this.state.reservation.stop_time} + onChange={value => { + this.setParams('stop_time', value[0] ? value[0] : this.state.reservation.stop_time); + this.setReservationParams('stop_time', value[0] ? value[0] : this.state.reservation.stop_time) + }} > + <input type="text" data-input className={`p-inputtext p-component ${this.state.errors.stop_time && this.state.touched.stop_time ? 'input-error' : ''}`} /> + <i className="fa fa-calendar" data-toggle style={{ position: "absolute", marginLeft: '-25px', marginTop: '5px', cursor: 'pointer' }} ></i> + <i className="fa fa-times" style={{ position: "absolute", marginLeft: '-50px', marginTop: '5px', cursor: 'pointer' }} + onClick={e => { this.setParams('stop_time', ''); this.setReservationParams('stop_time', '') }}></i> + </Flatpickr> + <label className={this.state.errors.stop_time && this.state.touched.stop_time ? "error" : "info"}> + {this.state.errors.stop_time && this.state.touched.stop_time ? this.state.errors.stop_time : ""} + </label> + </div> + </div> + + <div className="p-field p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Duration</label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <InputMask mask="999 99:99:99" + tooltip="Enter duration in (Days HH:mm:ss) format. Min-000 00:00:01 & Max-190 00:00:00." + tooltipOptions={this.tooltipOptions} + value={this.state.reservation.duration} + placeholder="DDD HH:mm:ss" + onChange={(e) =>{if(!e.value) {this.setDurationOrEndValue("stop_time")}}} + onComplete={(e) => { + if (this.isValidDuration(e.value)) { + this.setReservationParams('duration', e.value); + this.setParams('duration', e.value ? e.value : this.state.reservation.duration); + } + }} + onBlur={(e) => { + this.setParams('duration', e.value ? e.value : this.setDurationOrEndValue("stop_time")); + }}></InputMask> + <label className={this.state.errors.duration && this.state.touched.duration ? "error" : "info"}> + {this.state.errors.duration && this.state.touched.duration ? this.state.errors.duration : ""} + </label> + </div> + </div> </div> <div className="p-grid"> <div className="p-col-12"> - {this.state.paramsSchema?jeditor:""} + {this.state.paramsSchema ? jeditor : ""} </div> </div> - </div> + </div> - <div className="p-grid p-justify-start"> - <div className="p-col-1"> - <Button label="Save" className="p-button-primary" icon="pi pi-check" onClick={this.saveReservation} + + <div className="p-grid p-justify-start"> + <div className="p-col-1"> + <Button label="Save" className="p-button-primary" icon="pi pi-check" onClick={this.saveReservation} disabled={!this.state.validEditor || !this.state.validForm} data-testid="save-btn" /> - </div> - <div className="p-col-1"> - <Button label="Cancel" className="p-button-danger" icon="pi pi-times" onClick={this.checkIsDirty} /> + </div> + <div className="p-col-1"> + <Button label="Cancel" className="p-button-danger" icon="pi pi-times" onClick={this.checkIsDirty} /> + </div> </div> </div> - </div> - </> + </> } {/* Dialog component to show messages and get input */} <div className="p-grid" data-testid="confirm_dialog"> - <Dialog header={this.state.dialog.header} visible={this.state.dialogVisible} style={{width: '25vw'}} inputId="confirm_dialog" - modal={true} onHide={() => {this.setState({dialogVisible: false})}} - footer={<div> - <Button key="back" onClick={() => {this.setState({dialogVisible: false, redirect: `/reservation/list`});}} label="No" /> - <Button key="submit" type="primary" onClick={this.reset} label="Yes" /> - </div> - } > - <div className="p-grid"> - <div className="col-lg-2 col-md-2 col-sm-2" style={{margin: 'auto'}}> - <i className="pi pi-check-circle pi-large pi-success"></i> - </div> - <div className="col-lg-10 col-md-10 col-sm-10"> - {this.state.dialog.detail} - </div> + <Dialog header={this.state.dialog.header} visible={this.state.dialogVisible} style={{ width: '25vw' }} inputId="confirm_dialog" + modal={true} onHide={() => { this.setState({ dialogVisible: false }) }} + footer={<div> + <Button key="back" onClick={() => { this.setState({ dialogVisible: false, redirect: `/reservation/list` }); }} label="No" /> + <Button key="submit" type="primary" onClick={this.reset} label="Yes" /> + </div> + } > + <div className="p-grid"> + <div className="col-lg-2 col-md-2 col-sm-2" style={{ margin: 'auto' }}> + <i className="pi pi-check-circle pi-large pi-success"></i> </div> + <div className="col-lg-10 col-md-10 col-sm-10"> + {this.state.dialog.detail} + </div> + </div> </Dialog> <CustomDialog type="confirmation" visible={this.state.showDialog} width="40vw" - header={'Add Reservation'} message={'Do you want to leave this page? Your changes may not be saved.'} - content={''} onClose={this.close} onCancel={this.close} onSubmit={this.cancelCreate}> - </CustomDialog> + header={'Add Reservation'} message={'Do you want to leave this page? Your changes may not be saved.'} + content={''} onClose={this.cancelCreate} onCancel={this.close} onSubmit={this.cancelCreate}> + </CustomDialog> </div> </React.Fragment> ); diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.edit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.edit.js index 255eea3828b4cd62a3190ca0a4417ab2f87f5176..46e6f0f87d0ad4e0461e67ed4d31ae49e732c574 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.edit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.edit.js @@ -3,7 +3,7 @@ import { Redirect } from 'react-router-dom' import { Button } from 'primereact/button'; import { Dropdown } from 'primereact/dropdown'; -import {InputText } from 'primereact/inputtext'; +import { InputText } from 'primereact/inputtext'; import { InputTextarea } from 'primereact/inputtextarea'; import moment from 'moment'; @@ -11,7 +11,6 @@ import _ from 'lodash'; import Flatpickr from "react-flatpickr"; import { publish } from '../../App'; - import { CustomDialog } from '../../layout/components/CustomDialog'; import { appGrowl } from '../../layout/components/AppGrowl'; import AppLoader from '../../layout/components/AppLoader'; @@ -21,6 +20,8 @@ import UIConstants from '../../utils/ui.constants'; import ProjectService from '../../services/project.service'; import ReservationService from '../../services/reservation.service'; import UtilService from '../../services/util.service'; +import { InputMask } from 'primereact/inputmask'; +import UnitConverter from '../../utils/unit.converter'; export class ReservationEdit extends Component { constructor(props) { @@ -40,19 +41,21 @@ export class ReservationEdit extends Component { this.projects = []; // All projects to load project dropdown this.reservationTemplates = []; this.reservationStrategies = []; + this.tooltipOptions = UIConstants.tooltipOptions; this.setEditorOutput = this.setEditorOutput.bind(this); this.setEditorFunction = this.setEditorFunction.bind(this); this.checkIsDirty = this.checkIsDirty.bind(this); + this.isValidDuration = this.isValidDuration.bind(this); this.saveReservation = this.saveReservation.bind(this); this.close = this.close.bind(this); this.cancelEdit = this.cancelEdit.bind(this); - // Validateion Rules + // Validateion Rules this.formRules = { - name: {required: true, message: "Name can not be empty"}, - description: {required: true, message: "Description can not be empty"}, - start_time: {required: true, message: "Start Time can not be empty"}, + name: { required: true, message: "Name can not be empty" }, + description: { required: true, message: "Description can not be empty" }, + start_time: { required: true, message: "Start Time can not be empty" }, }; } @@ -65,21 +68,20 @@ export class ReservationEdit extends Component { * @param {Function} editorFunction */ setEditorFunction(editorFunction) { - this.setState({editorFunction: editorFunction}); + this.setState({ editorFunction: editorFunction }); } - + /** * Initialize the Reservation and related */ async initReservation() { - const reserId = this.props.match?this.props.match.params.id: null; - - const promises = [ ProjectService.getProjectList(), - ReservationService.getReservationTemplates(), - UtilService.getUTC(), - ReservationService.getReservationStrategyTemplates() - ]; - let emptyProjects = [{url: null, name: "Select Project"}]; + const reserId = this.props.match ? this.props.match.params.id : null; + const promises = [ProjectService.getProjectList(), + ReservationService.getReservationTemplates(), + UtilService.getUTC(), + ReservationService.getReservationStrategyTemplates() + ]; + let emptyProjects = [{ url: null, name: "Select Project" }]; Promise.all(promises).then(responses => { this.projects = emptyProjects.concat(responses[0]); this.reservationTemplates = responses[1]; @@ -88,7 +90,7 @@ export class ReservationEdit extends Component { let schema = { properties: {} }; - if(this.state.reservationTemplate) { + if (this.state.reservationTemplate) { schema = this.state.reservationTemplate.schema; } this.setState({ @@ -97,10 +99,10 @@ export class ReservationEdit extends Component { systemTime: systemTime }); this.getReservationDetails(reserId); - }); - - } + }); + } + /** * To get the reservation details from the backend using the service * @param {number} Reservation Id @@ -108,66 +110,69 @@ export class ReservationEdit extends Component { async getReservationDetails(id) { if (id) { await ReservationService.getReservation(id) - .then(async (reservation) => { - if (reservation) { - let reservationTemplate = this.reservationTemplates.find(reserTemplate => reserTemplate.id === reservation.specifications_template_id); - if (this.state.editorFunction) { - this.state.editorFunction(); - } - // no project then allow to select project from dropdown list - this.hasProject = reservation.project?true:false; - let schema = { - properties: {} - }; - if(reservationTemplate) { - schema = reservationTemplate.schema; - } - let project = this.projects.find(project => project.name === reservation.project_id); - reservation['project']= project ? project.name: null; - let strategyName = reservation.specifications_doc.activity.name; - let reservationStrategy = null; - if (strategyName) { - reservationStrategy = this.reservationStrategies.find(strategy => strategy.name === strategyName); - } else { - reservationStrategy= { - id: null, + .then(async (reservation) => { + if (reservation) { + reservation.duration = reservation.duration || 0; + reservation.duration = UnitConverter.getSecsToDDHHmmss(reservation.duration); //this.secondsToHms(reservation.duration); + let reservationTemplate = this.reservationTemplates.find(reserTemplate => reserTemplate.id === reservation.specifications_template_id); + if (this.state.editorFunction) { + this.state.editorFunction(); + } + // no project then allow to select project from dropdown list + this.hasProject = reservation.project ? true : false; + let schema = { + properties: {} + }; + if (reservationTemplate) { + schema = reservationTemplate.schema; + } + let project = this.projects.find(project => project.name === reservation.project_id); + reservation['project'] = project ? project.name : null; + let strategyName = reservation.specifications_doc.activity.name; + let reservationStrategy = null; + if (strategyName) { + reservationStrategy = this.reservationStrategies.find(strategy => strategy.name === strategyName); + } else { + reservationStrategy = { + id: null, + } } - } - this.setState({ - reservationStrategy: reservationStrategy, - reservation: reservation, - reservationTemplate: reservationTemplate, - paramsSchema: schema,}); - } else { - this.setState({redirect: "/not-found"}); - } - }); - } else { - this.setState({redirect: "/not-found"}); + this.setState({ + reservationStrategy: reservationStrategy, + reservation: reservation, + reservationTemplate: reservationTemplate, + paramsSchema: schema, + }); + } else { + this.setState({ redirect: "/not-found" }); + } + }); + } else { + this.setState({ redirect: "/not-found" }); } } close() { - this.setState({showDialog: false}); + this.setState({ showDialog: false }); } - + /** * Cancel edit and redirect to Reservation View page */ - cancelEdit() { - publish('edit-dirty', false); - this.props.history.goBack(); - this.setState({showDialog: false}); - this.props.history.goBack(); + cancelEdit() { + const reserId = this.props.match ? this.props.match.params.id : null; + publish('edit-dirty', false); + this.setState({ showDialog: false }); + this.props.history.goBack(); } /** * warn before cancel this page if any changes detected */ - checkIsDirty() { - if( this.state.isDirty ){ - this.setState({showDialog: true}); + checkIsDirty() { + if (this.state.isDirty) { + this.setState({ showDialog: true }); } else { this.cancelEdit(); } @@ -178,7 +183,7 @@ export class ReservationEdit extends Component { * If no argument passed for fieldName, validates all fields in the form. * @param {string} fieldName */ - validateForm(fieldName) { + validateForm(fieldName) { let validForm = false; let errors = this.state.errors; let validFields = this.state.validFields; @@ -190,13 +195,13 @@ export class ReservationEdit extends Component { const fieldValue = this.state.reservation[fieldName]; if (rule.required) { if (!fieldValue) { - errors[fieldName] = rule.message?rule.message:`${fieldName} is required`; - } else { + errors[fieldName] = rule.message ? rule.message : `${fieldName} is required`; + } else { validFields[fieldName] = true; } } - } - } else { + } + } else { errors = {}; validFields = {}; for (const fieldName in this.formRules) { @@ -204,14 +209,14 @@ export class ReservationEdit extends Component { const fieldValue = this.state.reservation[fieldName]; if (rule.required) { if (!fieldValue) { - errors[fieldName] = rule.message?rule.message:`${fieldName} is required`; - } else { + errors[fieldName] = rule.message ? rule.message : `${fieldName} is required`; + } else { validFields[fieldName] = true; } } } } - this.setState({errors: errors, validFields: validFields}); + this.setState({ errors: errors, validFields: validFields }); if (Object.keys(validFields).length === Object.keys(this.formRules).length) { validForm = true; delete errors['start_time']; @@ -227,7 +232,20 @@ export class ReservationEdit extends Component { errors['stop_time'] = "End Time cannot be same or before Start Time"; delete errors['start_time']; } - this.setState({errors: errors}); + this.setState({ errors: errors }); + } + if (fieldName === 'duration' && this.state.reservation.duration) { + var values = this.state.reservation.duration.split(' '); + var days = values[0]; + var dValues = values[1].split(':'); + delete errors['duration']; + if ((days *1 )===0 && (dValues[0] * 1) === 0 && (dValues[1] * 1) === 0 && (dValues[2] * 1) === 0) { + validForm = false; + if (!fieldName || fieldName === 'duration') { + errors['duration'] = "Duration cannot be zero"; + } + this.setState({ errors: errors }); + } } return validForm; } @@ -238,17 +256,17 @@ export class ReservationEdit extends Component { * @param {Date} toDate * @returns boolean */ - validateDates(fromDate, toDate) { + validateDates(fromDate, toDate) { if (fromDate && toDate && moment(toDate).isSameOrBefore(moment(fromDate))) { return false; } return true; } - /** - * This function is mainly added for Unit Tests. If this function is removed Unit Tests will fail. - */ - validateEditor() { + /** + * This function is mainly added for Unit Tests. If this function is removed Unit Tests will fail. + */ + validateEditor() { return this.validEditor; } @@ -257,22 +275,89 @@ export class ReservationEdit extends Component { * @param {string} key * @param {any} value */ - setParams(key, value, type) { + setParams(key, value, type) { let reservation = this.state.reservation; - switch(type) { + switch (type) { case 'NUMBER': { - reservation[key] = value?parseInt(value):0; + reservation[key] = value ? parseInt(value) : 0; break; } default: { - reservation[key] = value; + reservation[key] = value; break; } } - this.setState({reservation: reservation, validForm: this.validateForm(key), isDirty: true}); + this.setState({ reservation: reservation, validForm: this.validateForm(key), isDirty: true }, + () => { + this.setDurationOrEndValue(key); + }); publish('edit-dirty', true); } + /** + * Function to set the value for the dependant fields when value is set to one field. + * When start_time or stop_time is changed, duration will be updated accordingly. + * Similarly if duration is changed, stop_time is updated. + * @param {String} key - property name of the reservation + */ + setDurationOrEndValue = (key) => { + let state = this.state; + if ( key === 'start_time' || key === 'stop_time') { + if (this.state.reservation.start_time && this.state.reservation.stop_time) { + var delta = Math.abs(new Date(this.state.reservation.start_time) - new Date(this.state.reservation.stop_time)) / 1000; + let tempDuration = UnitConverter.getSecsToDDHHmmss(delta); + this.setDurationOrStopTime('duration', tempDuration); + } else if (key === 'start_time' && this.state.reservation.start_time && this.state.reservation.duration) { + let stopDate = UnitConverter.getEndDateFromDuration(this.state.reservation.start_time, this.state.reservation.duration); + this.setDurationOrStopTime('stop_time', stopDate); + } else if (key === 'stop_time' && !this.state.reservation.stop_time) { + this.setDurationOrStopTime('duration', ""); + } + } + else if (key === 'duration') { + if (this.state.reservation.start_time) { + let stopDate = UnitConverter.getEndDateFromDuration(this.state.reservation.start_time, this.state.reservation.duration); + this.setDurationOrStopTime('stop_time', stopDate); + } + } + } + + /** + * Function to set calcualted value for either duration or stop_time + * @param {String} key - name of the field + * @param {*} value - value to set for the field + */ + setDurationOrStopTime(key, value) { + let reservation = this.state.reservation; + reservation[key] = value; + this.setState({ reservation: reservation, validForm: this.validateForm(key), isDirty: true }); + } + + /** + * Function to validate the duration field. + * @param {String} value - Duration in format 'Days HH:mm:ss' + * @returns boolean + */ + isValidDuration(value) { + let errors = this.state.errors; + let touched = this.state.touched; + let reservation = this.state.reservation; + let validForm = this.state.validForm; + if (value.length === 12 && (value === "000 00:00:00" || + !value.match(/^([0-1]90 00:00:00)|([0-1][0-8][0-9] ([0-1]?\d|2[0-3]):([0-5]?\d):([0-5]?\d))$/))) { + errors.duration = "Not valid duration. Duration should be in Days Hours:minutes:seconds. Min - 000 00:00:01, Max - 190 00:00:00"; + touched.duration = true; + validForm = false; + } else { + delete errors["duration"]; + delete touched["duration"]; + validForm = this.validateForm(); + } + reservation.duration = value; + this.setState({errors: errors, touched: touched, reservation: reservation, validForm: validForm}); + return errors.duration?false:true; + } + /** * Set JEditor output * @param {*} jsonOutput @@ -281,66 +366,74 @@ export class ReservationEdit extends Component { setEditorOutput(jsonOutput, errors) { this.paramsOutput = jsonOutput; this.validEditor = errors.length === 0; - if ( !this.state.isDirty && this.state.paramsOutput && !_.isEqual(this.state.paramsOutput, jsonOutput) ) { - this.setState({ paramsOutput: jsonOutput, + if (!this.state.isDirty && this.state.paramsOutput && !_.isEqual(this.state.paramsOutput, jsonOutput)) { + this.setState({ + paramsOutput: jsonOutput, validEditor: errors.length === 0, validForm: this.validateForm(), - isDirty: true}); + isDirty: true + }); publish('edit-dirty', true); - } else { - this.setState({ paramsOutput: jsonOutput, + } else { + this.setState({ + paramsOutput: jsonOutput, validEditor: errors.length === 0, - validForm: this.validateForm()}); + validForm: this.validateForm() + }); } } - + /** * Function to set form values to the Reservation object * @param {string} key * @param {object} value */ - setReservationParams(key, value) { + setReservationParams(key, value) { let reservation = _.cloneDeep(this.state.reservation); reservation[key] = value; - if ( !this.state.isDirty && !_.isEqual(this.state.reservation, reservation) ) { - this.setState({reservation: reservation, validForm: this.validateForm(key), validEditor: this.validateEditor(), touched: { - ...this.state.touched, - [key]: true - }, isDirty: true}); + if (!this.state.isDirty && !_.isEqual(this.state.reservation, reservation)) { + this.setState({ + reservation: reservation, validForm: this.validateForm(key), validEditor: this.validateEditor(), touched: { + ...this.state.touched, + [key]: true + }, isDirty: true + }); publish('edit-dirty', true); - } else { - this.setState({reservation: reservation, validForm: this.validateForm(key), validEditor: this.validateEditor(),touched: { - ...this.state.touched, - [key]: true - }}); + } else { + this.setState({ + reservation: reservation, validForm: this.validateForm(key), validEditor: this.validateEditor(), touched: { + ...this.state.touched, + [key]: true + } + }); } } /** * Update reservation */ - async saveReservation(){ + async saveReservation() { let reservation = this.state.reservation; let project = this.projects.find(project => project.name === reservation.project); reservation['start_time'] = moment(reservation['start_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT); - reservation['stop_time'] = (reservation['stop_time'] && reservation['stop_time'] !== 'Invalid date') ?moment(reservation['stop_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT):null; - reservation['project']= project ? project.url: null; - reservation['specifications_doc']= this.paramsOutput; - reservation = await ReservationService.updateReservation(reservation); - if (reservation && reservation.id){ - appGrowl.show({severity: 'success', summary: 'Success', detail: 'Reservation updated successfully.'}); + reservation['stop_time'] = (reservation['stop_time'] && reservation['stop_time'] !== 'Invalid date') ? moment(reservation['stop_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT) : null; + reservation['project'] = project ? project.url : null; + reservation['specifications_doc'] = this.paramsOutput; + reservation = await ReservationService.updateReservation(reservation); + if (reservation && reservation.id) { + appGrowl.show({ severity: 'success', summary: 'Success', detail: 'Reservation updated successfully.' }); this.props.history.push({ pathname: `/reservation/view/${this.props.match.params.id}`, - }); + }); publish('edit-dirty', false); - } else { - appGrowl.show({severity: 'error', summary: 'Error Occured', detail: 'Unable to update Reservation', showDialog: false, isDirty: false}); + } else { + appGrowl.show({ severity: 'error', summary: 'Error Occured', detail: 'Unable to update Reservation', showDialog: false, isDirty: false }); } } render() { if (this.state.redirect) { - return <Redirect to={ {pathname: this.state.redirect} }></Redirect> + return <Redirect to={{ pathname: this.state.redirect }}></Redirect> } let jeditor = null; if (this.state.reservationTemplate) { @@ -348,107 +441,53 @@ export class ReservationEdit extends Component { delete this.state.reservation.specifications_doc.$id; delete this.state.reservation.specifications_doc.$schema; } - jeditor = React.createElement(Jeditor, {title: "Reservation Parameters", - schema: this.state.reservationTemplate.schema, - initValue: this.state.reservation.specifications_doc, - disabled: false, - callback: this.setEditorOutput, - parentFunction: this.setEditorFunction - }); + jeditor = React.createElement(Jeditor, { + title: "Reservation Parameters", + schema: this.state.reservationTemplate.schema, + initValue: this.state.reservation.specifications_doc, + disabled: false, + callback: this.setEditorOutput, + parentFunction: this.setEditorFunction + }); } return ( <React.Fragment> - <PageHeader location={this.props.location} title={'Reservation - Edit'} actions={[{icon:'fa-window-close', - title:'Click to Close Reservation - Edit', type: 'button', actOn: 'click', props:{ callback: this.checkIsDirty }}]}/> + <PageHeader location={this.props.location} title={'Reservation - Edit'} actions={[{ + icon: 'fa-window-close', + title: 'Click to Close Reservation - Edit', type: 'button', actOn: 'click', props: { callback: this.checkIsDirty } + }]} /> - { this.state.isLoading? <AppLoader /> : this.state.reservation && + { this.state.isLoading ? <AppLoader /> : this.state.reservation && <React.Fragment> <div> - <div className="p-fluid"> - <div className="p-field p-grid"> - <label htmlFor="reservationname" className="col-lg-2 col-md-2 col-sm-12">Name <span style={{color:'red'}}>*</span></label> - <div className="col-lg-3 col-md-3 col-sm-12"> - <InputText className={(this.state.errors.name && this.state.touched.name) ?'input-error':''} id="reservationname" data-testid="name" - tooltip="Enter name of the Reservation Name" tooltipOptions={this.tooltipOptions} maxLength="128" - ref={input => {this.nameInput = input;}} - value={this.state.reservation.name} autoFocus - onChange={(e) => this.setReservationParams('name', e.target.value)} - onBlur={(e) => this.setReservationParams('name', e.target.value)}/> - <label className={(this.state.errors.name && this.state.touched.name)?"error":"info"}> - {this.state.errors.name && this.state.touched.name ? this.state.errors.name : "Max 128 characters"} - </label> - </div> - <div className="col-lg-1 col-md-1 col-sm-12"></div> - <label htmlFor="description" className="col-lg-2 col-md-2 col-sm-12">Description <span style={{color:'red'}}>*</span></label> - <div className="col-lg-3 col-md-3 col-sm-12"> - <InputTextarea className={(this.state.errors.description && this.state.touched.description) ?'input-error':''} rows={3} cols={30} - tooltip="Longer description of the Reservation" - tooltipOptions={this.tooltipOptions} - maxLength="128" - data-testid="description" - value={this.state.reservation.description} - onChange={(e) => this.setReservationParams('description', e.target.value)} - onBlur={(e) => this.setReservationParams('description', e.target.value)}/> - <label className={(this.state.errors.description && this.state.touched.description) ?"error":"info"}> - {(this.state.errors.description && this.state.touched.description) ? this.state.errors.description : "Max 255 characters"} - </label> - </div> - </div> - <div className="p-field p-grid"> - <label className="col-lg-2 col-md-2 col-sm-12">Start Time<span style={{color:'red'}}>*</span></label> + <div className="p-fluid"> + <div className="p-field p-grid"> + <label htmlFor="reservationname" className="col-lg-2 col-md-2 col-sm-12">Name <span style={{ color: 'red' }}>*</span></label> <div className="col-lg-3 col-md-3 col-sm-12"> - <Flatpickr data-enable-time data-input options={{ - "inlineHideInput": true, - "wrap": true, - "enableSeconds": true, - "time_24hr": true, - "minuteIncrement": 1, - "allowInput": true, - "defaultDate": this.state.systemTime.format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT), - "defaultHour": this.state.systemTime.hours(), - "defaultMinute": this.state.systemTime.minutes() - }} - title="Start of this reservation" - value={this.state.reservation.start_time} - onChange= {value => {this.setParams('start_time', value[0]?value[0]:this.state.reservation.start_time); - this.setReservationParams('start_time', value[0]?value[0]:this.state.reservation.start_time)}} > - <input type="text" data-input className={`p-inputtext p-component ${this.state.errors.start_time && this.state.touched.start_time?'input-error':''}`} /> - <i className="fa fa-calendar" data-toggle style={{position: "absolute", marginLeft: '-25px', marginTop:'5px', cursor: 'pointer'}} ></i> - <i className="fa fa-times" style={{position: "absolute", marginLeft: '-50px', marginTop:'5px', cursor: 'pointer'}} - onClick={e => {this.setParams('start_time', ''); this.setReservationParams('start_time', '')}}></i> - </Flatpickr> - <label className={this.state.errors.start_time && this.state.touched.start_time?"error":"info"}> - {this.state.errors.start_time && this.state.touched.start_time ? this.state.errors.start_time : ""} + <InputText className={(this.state.errors.name && this.state.touched.name) ? 'input-error' : ''} id="reservationname" data-testid="name" + tooltip="Enter name of the Reservation" tooltipOptions={this.tooltipOptions} maxLength="128" + ref={input => { this.nameInput = input; }} + value={this.state.reservation.name} autoFocus + onChange={(e) => this.setReservationParams('name', e.target.value)} + onBlur={(e) => this.setReservationParams('name', e.target.value)} /> + <label className={(this.state.errors.name && this.state.touched.name) ? "error" : "info"}> + {this.state.errors.name && this.state.touched.name ? this.state.errors.name : "Max 128 characters"} </label> </div> <div className="col-lg-1 col-md-1 col-sm-12"></div> - - <label className="col-lg-2 col-md-2 col-sm-12">End time</label> + <label htmlFor="description" className="col-lg-2 col-md-2 col-sm-12">Description <span style={{ color: 'red' }}>*</span></label> <div className="col-lg-3 col-md-3 col-sm-12"> - <Flatpickr data-enable-time data-input options={{ - "inlineHideInput": true, - "wrap": true, - "enableSeconds": true, - "time_24hr": true, - "minuteIncrement": 1, - "allowInput": true, - "minDate": this.state.reservation.stop_time?this.state.reservation.stop_time.toDate:'', - "defaultDate": this.state.systemTime.format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT), - "defaultHour": this.state.systemTime.hours(), - "defaultMinute": this.state.systemTime.minutes() - }} - title="End of this reservation. If empty, then this reservation is indefinite." - value={this.state.reservation.stop_time} - onChange= {value => {this.setParams('stop_time', value[0]?value[0]:this.state.reservation.stop_time); - this.setReservationParams('stop_time', value[0]?value[0]:this.state.reservation.stop_time)}} > - <input type="text" data-input className={`p-inputtext p-component ${this.state.errors.stop_time && this.state.touched.stop_time?'input-error':''}`} /> - <i className="fa fa-calendar" data-toggle style={{position: "absolute", marginLeft: '-25px', marginTop:'5px', cursor: 'pointer'}} ></i> - <i className="fa fa-times" style={{position: "absolute", marginLeft: '-50px', marginTop:'5px', cursor: 'pointer'}} - onClick={e => {this.setParams('stop_time', ''); this.setReservationParams('stop_time', '')}}></i> - </Flatpickr> - <label className={this.state.errors.stop_time && this.state.touched.stop_time?"error":"info"}> - {this.state.errors.stop_time && this.state.touched.stop_time ? this.state.errors.stop_time : ""} + <InputTextarea className={(this.state.errors.description && this.state.touched.description) ? 'input-error' : ''} rows={3} cols={30} + tooltip="Longer description of the Reservation" + tooltipOptions={this.tooltipOptions} + maxLength="128" + data-testid="description" + value={this.state.reservation.description} + onChange={(e) => this.setReservationParams('description', e.target.value)} + onBlur={(e) => this.setReservationParams('description', e.target.value)} /> + <label className={(this.state.errors.description && this.state.touched.description) ? "error" : "info"}> + {(this.state.errors.description && this.state.touched.description) ? this.state.errors.description : "Max 255 characters"} </label> </div> </div> @@ -456,19 +495,19 @@ export class ReservationEdit extends Component { <div className="p-field p-grid"> <label htmlFor="project" className="col-lg-2 col-md-2 col-sm-12">Project</label> <div className="col-lg-3 col-md-3 col-sm-12" data-testid="project" > - <Dropdown inputId="project" optionLabel="name" optionValue="name" - tooltip="Project" tooltipOptions={this.tooltipOptions} - value={this.state.reservation.project} - options={this.projects} - onChange={(e) => {this.setParams('project',e.value)}} - placeholder="Select Project" - disabled={this.hasProject} - /> - <label className={(this.state.errors.project && this.state.touched.project) ?"error":"info"}> - {(this.state.errors.project && this.state.touched.project) ? this.state.errors.project : this.state.reservation.project? '': "Select Project"} + <Dropdown inputId="project" optionLabel="name" optionValue="name" + tooltip="Project" tooltipOptions={this.tooltipOptions} + value={this.state.reservation.project} + options={this.projects} + onChange={(e) => { this.setParams('project', e.value) }} + placeholder="Select Project" + disabled={this.hasProject} + /> + <label className={(this.state.errors.project && this.state.touched.project) ? "error" : "info"}> + {(this.state.errors.project && this.state.touched.project) ? this.state.errors.project : "Select Project"} </label> </div> - {/* <div className="col-lg-1 col-md-1 col-sm-12"></div> + {/* <div className="col-lg-1 col-md-1 col-sm-12"></div> <label htmlFor="strategy" className="col-lg-2 col-md-2 col-sm-12">Reservation Strategy</label> <div className="col-lg-3 col-md-3 col-sm-12" data-testid="strategy" > {this.state.reservationStrategy.id && @@ -481,33 +520,119 @@ export class ReservationEdit extends Component { disabled= {true} /> } </div> */} + </div> + + <div className="p-field p-grid"> + <label htmlFor="start-time" className="col-lg-2 col-md-2 col-sm-12" >Start Time<span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <Flatpickr data-enable-time data-input options={{ + "inlineHideInput": true, + "wrap": true, + "enableSeconds": true, + "time_24hr": true, + "minuteIncrement": 1, + "allowInput": true, + "defaultDate": this.state.systemTime.format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT), + "defaultHour": this.state.systemTime.hours(), + "defaultMinute": this.state.systemTime.minutes() + }} + title="Start of this reservation" + value={this.state.reservation.start_time} + onChange={value => { + this.setParams('start_time', value[0] ? value[0] : this.state.reservation.start_time); + this.setReservationParams('start_time', value[0] ? value[0] : this.state.reservation.start_time) + }} > + <input type="text" data-input className={`p-inputtext p-component ${this.state.errors.start_time && this.state.touched.start_time ? 'input-error' : ''}`} /> + <i className="fa fa-calendar" data-toggle style={{ position: "absolute", marginLeft: '-25px', marginTop: '5px', cursor: 'pointer' }} ></i> + <i className="fa fa-times" style={{ position: "absolute", marginLeft: '-50px', marginTop: '5px', cursor: 'pointer' }} + onClick={e => { this.setParams('start_time', ''); this.setReservationParams('start_time', '') }}></i> + </Flatpickr> + <label className={this.state.errors.start_time && this.state.touched.start_time ? "error" : "info"}> + {this.state.errors.start_time && this.state.touched.start_time ? this.state.errors.start_time : ""} + </label> + </div> + <div className="col-lg-1 col-md-1 col-sm-12"></div> + <label className="col-lg-2 col-md-2 col-sm-12">End time</label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <Flatpickr data-enable-time data-input options={{ + "inlineHideInput": true, + "wrap": true, + "enableSeconds": true, + "time_24hr": true, + "minuteIncrement": 1, + "allowInput": true, + "minDate": this.state.reservation.stop_time ? this.state.reservation.stop_time.toDate : '', + "defaultDate": this.state.systemTime.format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT), + "defaultHour": this.state.systemTime.hours(), + "defaultMinute": this.state.systemTime.minutes() + }} + title="End of this reservation. If empty, then this reservation is indefinite." + value={this.state.reservation.stop_time} + onChange={value => { + this.setParams('stop_time', value[0] ? value[0] : this.state.reservation.stop_time); + this.setReservationParams('stop_time', value[0] ? value[0] : this.state.reservation.stop_time) + }} > + <input type="text" data-input className={`p-inputtext p-component ${this.state.errors.stop_time && this.state.touched.stop_time ? 'input-error' : ''}`} /> + <i className="fa fa-calendar" data-toggle style={{ position: "absolute", marginLeft: '-25px', marginTop: '5px', cursor: 'pointer' }} ></i> + <i className="fa fa-times" style={{ position: "absolute", marginLeft: '-50px', marginTop: '5px', cursor: 'pointer' }} + onClick={e => { this.setParams('stop_time', ''); this.setReservationParams('stop_time', '') }}></i> + </Flatpickr> + <label className={this.state.errors.stop_time && this.state.touched.stop_time ? "error" : "info"}> + {this.state.errors.stop_time && this.state.touched.stop_time ? this.state.errors.stop_time : ""} + </label> + </div> + </div> + <div className="p-field p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Duration</label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <InputMask mask="999 99:99:99" + tooltip="Enter duration in (Days HH:mm:ss) format. Min-000 00:00:01 & Max-190 00:00:00." + tooltipOptions={this.tooltipOptions} + value={this.state.reservation.duration} + placeholder="DDD HH:MM:SS" + onChange={(e) =>{if(!e.value) {this.setDurationOrEndValue("stop_time")}}} + onComplete={(e) => { + if (this.isValidDuration(e.value)) { + this.setReservationParams('duration', e.value); + this.setParams('duration', e.value ? e.value : this.state.reservation.duration); + } + }} + onBlur={(e) => { + this.setParams('duration', e.value ? e.value : this.state.reservation.duration); + }}></InputMask> + <label className={this.state.errors.duration && this.state.touched.duration ? "error" : "info"}> + {this.state.errors.duration && this.state.touched.duration ? this.state.errors.duration : ""} + </label> + </div> </div> <div className="p-grid"> <div className="p-col-12"> - {this.state.paramsSchema?jeditor:""} + {this.state.paramsSchema ? jeditor : ""} </div> </div> - </div> + </div> - <div className="p-grid p-justify-start"> - <div className="p-col-1"> - <Button label="Save" className="p-button-primary" icon="pi pi-check" onClick={this.saveReservation} + <div className="p-grid p-justify-start"> + <div className="p-col-1"> + <Button label="Save" className="p-button-primary" icon="pi pi-check" onClick={this.saveReservation} disabled={!this.state.validEditor || !this.state.validForm} data-testid="save-btn" /> - </div> - <div className="p-col-1"> - <Button label="Cancel" className="p-button-danger" icon="pi pi-times" onClick={this.checkIsDirty} /> + </div> + <div className="p-col-1"> + <Button label="Cancel" className="p-button-danger" icon="pi pi-times" onClick={this.checkIsDirty} /> + </div> </div> </div> - </div> - + </React.Fragment> } + <CustomDialog type="confirmation" visible={this.state.showDialog} width="40vw" - header={'Edit Reservation'} message={'Do you want to leave this page? Your changes may not be saved.'} - content={''} onClose={this.close} onCancel={this.close} onSubmit={this.cancelEdit}> - </CustomDialog> + header={'Edit Reservation'} message={'Do you want to leave this page? Your changes may not be saved.'} + content={''} onClose={this.cancelEdit} onCancel={this.close} onSubmit={this.cancelEdit}> + </CustomDialog> + </React.Fragment> ); } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js index a1192925ef42f23a809a60da71488e4d3430e7a9..22ddbc9addc2b443f597267cffd26ede7e25dfcc 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js @@ -43,7 +43,7 @@ export class ReservationList extends Component{ format:UIConstants.CALENDAR_DATETIME_FORMAT }, duration:{ - name:"Duration (HH:mm:ss)", + name:"Duration (Days HH:mm:ss)", format:UIConstants.CALENDAR_TIME_FORMAT }, type: { @@ -86,7 +86,7 @@ export class ReservationList extends Component{ optionalcolumns: [{ }], columnclassname: [{ - "Duration (HH:mm:ss)":"filter-input-75", + "Duration (Days HH:mm:ss)":"filter-input-75", "Reservation type":"filter-input-100", "Subject":"filter-input-75", "Planned":"filter-input-50", @@ -143,7 +143,7 @@ export class ReservationList extends Component{ reservation['stop_time']= 'Unknown'; } else { let duration = reservation.duration; - reservation.duration = UnitService.getSecsToHHmmss(reservation.duration); + reservation.duration = UnitService.getSecsToDDHHmmss(reservation.duration); reservation['stop_time']= moment(reservation['stop_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT); } reservation['start_time']= moment(reservation['start_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT); diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.view.js index 2e0c8fc3074ea65abdd83ccd06974d00c9665b0d..7a94d01dc16dbe5f7d4419347308f4e89bb2fcc8 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.view.js @@ -12,6 +12,7 @@ import { appGrowl } from '../../layout/components/AppGrowl'; import AppLoader from '../../layout/components/AppLoader'; import PageHeader from '../../layout/components/PageHeader'; import ReservationService from '../../services/reservation.service'; +import UnitConverter from '../../utils/unit.converter'; export class ReservationView extends Component { constructor(props) { @@ -176,6 +177,8 @@ export class ReservationView extends Component { <span className="col-lg-4 col-md-4 col-sm-12">{(this.state.reservation.project_id)?this.state.reservation.project_id:''}</span> {/* <label className="col-lg-2 col-md-2 col-sm-12">Reservation Strategy</label> <span className="col-lg-4 col-md-4 col-sm-12">{this.state.reservation.specifications_doc.activity.name}</span> */} + <label className="col-lg-2 col-md-2 col-sm-12">Duration (Days HH:mm:ss)</label> + <span className="col-lg-4 col-md-4 col-sm-12">{(this.state.reservation.duration)?UnitConverter.getSecsToDDHHmmss(this.state.reservation.duration):'Unknown'}</span> </div> <div className="p-fluid"> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js b/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js index c4f80c5163ab31be1dc4791e730f32be215eeda2..1f135a9fd6fd152fab1765bb0909f1886c72a260 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js @@ -1,62 +1,96 @@ import _, { round } from 'lodash'; +import moment from "moment"; const UnitConverter = { - resourceUnitMap: {'time':{display: 'Hours', conversionFactor: 3600, mode:'decimal', minFractionDigits:0, maxFractionDigits: 2 }, - 'bytes': {display: 'TB', conversionFactor: (1024*1024*1024*1024), mode:'decimal', minFractionDigits:0, maxFractionDigits: 3}, - 'number': {display: 'Numbers', conversionFactor: 1, mode:'decimal', minFractionDigits:0, maxFractionDigits: 0}, - 'days': {display: 'Days', conversionFactor: (3600*24), mode:'decimal', minFractionDigits:0, maxFractionDigits: 0}}, + resourceUnitMap: { + 'time': { display: 'Hours', conversionFactor: 3600, mode: 'decimal', minFractionDigits: 0, maxFractionDigits: 2 }, + 'bytes': { display: 'TB', conversionFactor: (1024 * 1024 * 1024 * 1024), mode: 'decimal', minFractionDigits: 0, maxFractionDigits: 3 }, + 'number': { display: 'Numbers', conversionFactor: 1, mode: 'decimal', minFractionDigits: 0, maxFractionDigits: 0 }, + 'days': { display: 'Days', conversionFactor: (3600 * 24), mode: 'decimal', minFractionDigits: 0, maxFractionDigits: 0 } + }, - getDBResourceUnit: function() { + getDBResourceUnit: function () { }, - getUIResourceUnit: function(type, value) { - try{ - if(this.resourceUnitMap[type]){ - var retval = Number.parseFloat(value/(this.resourceUnitMap[type].conversionFactor)).toFixed(this.resourceUnitMap[type].maxFractionDigits) + getUIResourceUnit: function (type, value) { + try { + if (this.resourceUnitMap[type]) { + var retval = Number.parseFloat(value / (this.resourceUnitMap[type].conversionFactor)).toFixed(this.resourceUnitMap[type].maxFractionDigits) return retval; } - - }catch(error){ - console.error('[unit.converter.getUIResourceUnit]',error); + + } catch (error) { + console.error('[unit.converter.getUIResourceUnit]', error); } return value; }, - getSecsToHHmmss: function(seconds) { - if (seconds) { - const hh = Math.floor(seconds/3600); - const mm = Math.floor((seconds - hh*3600) / 60 ); - const ss = +((seconds -(hh*3600)-(mm*60)) / 1); - return (hh<10?`0${hh}`:`${hh}`) + ':' + (mm<10?`0${mm}`:`${mm}`) + ':' + (ss<10?`0${ss}`:`${ss}`); + + /** + * Function to convert the duration to string format (Days HH:mm:ss). + * The days part is of 3 characters prefixed with 0s if it is less than 3 characters. + * @param {Number} duration - Duration in seconds + * @returns String - Formatted to 'Day HH:mm:ss' format. + */ + getSecsToDDHHmmss: function(duration) { + var days = Math.floor(duration / 86400); + duration -= days * 86400; + + return `${(days+"").padStart(3,0)} ${this.getSecsToHHmmss(duration)}`; + }, + + /** + * Function to get a date (to date) offset from another date(from date) by certain duration. + * The duration is defined in 'Days Hours:minutes:seconds' format. + * @param {String} startdate - string of a date object + * @param {String} duration - duration in string format 'DDD HH:mm:ss' + * @returns + */ + getEndDateFromDuration: function(startdate, duration) { + var values = duration.split(' '); + var days = values[0]; + let tempStart = moment(startdate); + let tempEnd = _.clone(tempStart); + tempEnd.add(days, 'days'); + tempEnd.add(this.getHHmmssToSecs(values[1]), 'seconds'); + return tempEnd.toDate(); + }, + + getSecsToHHmmss: function (seconds) { + if (seconds >= 0) { + const hh = Math.floor(seconds / 3600); + const mm = Math.floor((seconds - hh * 3600) / 60); + const ss = +((seconds - (hh * 3600) - (mm * 60)) / 1); + return (hh < 10 ? `0${hh}` : `${hh}`) + ':' + (mm < 10 ? `0${mm}` : `${mm}`) + ':' + (ss < 10 ? `0${ss}` : `${ss}`); } return seconds; }, - getHHmmssToSecs: function(seconds) { + getHHmmssToSecs: function (seconds) { if (seconds) { const strSeconds = _.split(seconds, ":"); - return strSeconds[0]*3600 + strSeconds[1]*60 + Number(strSeconds[2]); + return strSeconds[0] * 3600 + strSeconds[1] * 60 + Number(strSeconds[2]); } return 0; }, - radiansToDegree: function(object) { - for(let type in object) { + radiansToDegree: function (object) { + for (let type in object) { if (type === 'transit_offset') { continue; - }else if (typeof object[type] === 'object') { - this.radiansToDegree(object[type]); + } else if (typeof object[type] === 'object') { + this.radiansToDegree(object[type]); } else { object[type] = (object[type] * 180) / Math.PI; } } }, degreeToRadians(object) { - for(let type in object) { + for (let type in object) { if (type === 'transit_offset') { continue; } else if (typeof object[type] === 'object') { this.degreeToRadians(object[type]); } else { - object[type] = object[type] * (Math.PI/180); + object[type] = object[type] * (Math.PI / 180); } } }, @@ -64,23 +98,23 @@ const UnitConverter = { * Function to convert Angle 1 & 2 input value for UI. */ getAngleInput(prpInput, isDegree) { - if (prpInput){ - const isNegative = prpInput<0; - prpInput = prpInput * (isNegative?-1:1); + if (prpInput) { + const isNegative = prpInput < 0; + prpInput = prpInput * (isNegative ? -1 : 1); const degrees = prpInput * 180 / Math.PI; if (isDegree) { const dd = Math.floor(prpInput * 180 / Math.PI); - const mm = Math.floor((degrees-dd) * 60); - const ss = round((degrees-dd-(mm/60)) * 3600,4); - return (isNegative?'-':'') + (dd<10?`0${dd}`:`${dd}`) + 'd' + (mm<10?`0${mm}`:`${mm}`) + 'm' + (ss<10?`0${ss}`:`${ss}`) + 's'; - } else { - const hh = Math.floor(degrees/15); - const mm = Math.floor((degrees - (hh*15))/15 * 60 ); - const ss = round((degrees -(hh*15)-(mm*15/60))/15 * 3600, 4); - return (hh<10?`0${hh}`:`${hh}`) + 'h' + (mm<10?`0${mm}`:`${mm}`) + 'm' + (ss<10?`0${ss}`:`${ss}`) + 's'; + const mm = Math.floor((degrees - dd) * 60); + const ss = round((degrees - dd - (mm / 60)) * 3600, 4); + return (isNegative ? '-' : '') + (dd < 10 ? `0${dd}` : `${dd}`) + 'd' + (mm < 10 ? `0${mm}` : `${mm}`) + 'm' + (ss < 10 ? `0${ss}` : `${ss}`) + 's'; + } else { + const hh = Math.floor(degrees / 15); + const mm = Math.floor((degrees - (hh * 15)) / 15 * 60); + const ss = round((degrees - (hh * 15) - (mm * 15 / 60)) / 15 * 3600, 4); + return (hh < 10 ? `0${hh}` : `${hh}`) + 'h' + (mm < 10 ? `0${mm}` : `${mm}`) + 'm' + (ss < 10 ? `0${ss}` : `${ss}`) + 's'; } } else { - return isDegree?"0d0m0s":'0h0m0s'; + return isDegree ? "0d0m0s" : '0h0m0s'; } }, @@ -88,17 +122,17 @@ const UnitConverter = { * Function to convert Angle 1 & 2 input value for Backend. */ getAngleOutput(prpOutput, isDegree) { - if(prpOutput){ + if (prpOutput) { const splitOutput = prpOutput.split(':'); - const seconds = splitOutput[2]?splitOutput[2].split('.')[0]:splitOutput[2]; + const seconds = splitOutput[2] ? splitOutput[2].split('.')[0] : splitOutput[2]; let milliSeconds = prpOutput.split('.')[1] || '0000'; - milliSeconds = milliSeconds.padEnd(4,0); + milliSeconds = milliSeconds.padEnd(4, 0); if (isDegree) { - return ((splitOutput[0]*1 + splitOutput[1]/60 + seconds/3600 + milliSeconds/36000000)*Math.PI/180); - } else { - return ((splitOutput[0]*15 + splitOutput[1]/4 + seconds/240 + milliSeconds/2400000)*Math.PI/180); + return ((splitOutput[0] * 1 + splitOutput[1] / 60 + seconds / 3600 + milliSeconds / 36000000) * Math.PI / 180); + } else { + return ((splitOutput[0] * 15 + splitOutput[1] / 4 + seconds / 240 + milliSeconds / 2400000) * Math.PI / 180); } - }else{ + } else { return "00:00:00.0000"; } }, @@ -113,19 +147,19 @@ const UnitConverter = { getAngleInputType(input) { if (input.match(/^\-?((\d0?d(0?0m)(0?0(\.\d{1,4})?s))|(([0-8]?\d)d(([0-5]?\d)m)(([0-5]?\d)(\.\d{1,4})?s)))$/)) { return 'dms'; - } else if (input.match(/^([0-1]?\d|2[0-3])h([0-5]?\d)m([0-5]?\d)(\.\d{1,4})?s$/)) { + } else if (input.match(/^([0-1]?\d|2[0-3])h([0-5]?\d)m([0-5]?\d)(\.\d{1,4})?s$/)) { return 'hms'; - } else if (input.match(/^-?((\d0(.0{1,4})?)|([0-8]?\d)(\.\d{1,4})?) ?d(egree)?s?$/)) { + } else if (input.match(/^-?((\d0(.0{1,4})?)|([0-8]?\d)(\.\d{1,4})?) ?d(egree)?s?$/)) { return 'degrees'; - } else if (input.match(/^([0-1]?\d|2[0-3])(\.\d{1,4})? ?h(our)?s?$/)) { + } else if (input.match(/^([0-1]?\d|2[0-3])(\.\d{1,4})? ?h(our)?s?$/)) { return 'hours'; - } else if (input.match(/^\-?((\d0?:(00:)(00))|(([0-8]\d):(([0-5]\d):)(([0-5]\d)(\.\d{1,4})?))) ?d(egree)?s?$/)) { + } else if (input.match(/^\-?((\d0?:(00:)(00))|(([0-8]\d):(([0-5]\d):)(([0-5]\d)(\.\d{1,4})?))) ?d(egree)?s?$/)) { return 'deg_format'; - } else if (input.match(/^([0-1]?\d|2[0-3]):([0-5]?\d):([0-5]?\d)(\.\d{1,4})? ?h(our)?s?$/)) { + } else if (input.match(/^([0-1]?\d|2[0-3]):([0-5]?\d):([0-5]?\d)(\.\d{1,4})? ?h(our)?s?$/)) { return 'hour_format'; - } else if (input.match(/^\-?[0-6](\.\d{1,20})?$/)) { + } else if (input.match(/^\-?[0-6](\.\d{1,20})?$/)) { return 'radians'; - } else { + } else { return null; } }, @@ -137,29 +171,29 @@ const UnitConverter = { parseAngle(angle) { let radians = 0; const angleType = this.getAngleInputType(angle); - switch(angleType) { - case 'dms' : { + switch (angleType) { + case 'dms': { radians = this.convertAngleToRadian(angle); break; } - case 'hms' : { + case 'hms': { radians = this.convertAngleToRadian(angle); break; } - case 'degrees' : { - radians = this.convertToRadians(angle.replace('d','').replace('egree','').replace('s','').replace(' ','')); + case 'degrees': { + radians = this.convertToRadians(angle.replace('d', '').replace('egree', '').replace('s', '').replace(' ', '')); break; } - case 'hours' : { - radians = this.convertToRadians(angle.replace('h','').replace('our','').replace('s','').replace(' ','') * 15); + case 'hours': { + radians = this.convertToRadians(angle.replace('h', '').replace('our', '').replace('s', '').replace(' ', '') * 15); break; } - case 'deg_format' : { - radians = this.getAngleOutput(angle.replace('d','').replace('egree','').replace('s','').replace(' ',''), true); + case 'deg_format': { + radians = this.getAngleOutput(angle.replace('d', '').replace('egree', '').replace('s', '').replace(' ', ''), true); break; } - case 'hour_format' : { - radians = this.getAngleOutput(angle.replace('h','').replace('our','').replace('s','').replace(' ',''), false); + case 'hour_format': { + radians = this.getAngleOutput(angle.replace('h', '').replace('our', '').replace('s', '').replace(' ', ''), false); break; } case 'radians': { @@ -178,7 +212,7 @@ const UnitConverter = { * @returns */ convertToRadians(angle) { - return angle * Math.PI /180; + return angle * Math.PI / 180; }, /** * Converts a formatted string to a radian value @@ -188,18 +222,18 @@ const UnitConverter = { convertAngleToRadian(angle) { let radian = 0; const isDegree = angle.indexOf('d') > 0; - const degreeHourSplit = isDegree?angle.split("d"):angle.split("h"); + const degreeHourSplit = isDegree ? angle.split("d") : angle.split("h"); let degreeHour = degreeHourSplit[0]; - const isNegativeAngle = parseInt(degreeHour)<0; - degreeHour = isNegativeAngle?degreeHour*-1:degreeHour; + const isNegativeAngle = parseInt(degreeHour) < 0; + degreeHour = isNegativeAngle ? degreeHour * -1 : degreeHour; const minuteSplit = degreeHourSplit[1].split('m'); const minute = minuteSplit[0]; - const second = minuteSplit[1].replace('s',''); + const second = minuteSplit[1].replace('s', ''); if (isDegree) { - radian = this.convertToRadians((degreeHour*1 + minute/60 + second/3600)); - radian = isNegativeAngle?radian*-1:radian; - } else { - radian = this.convertToRadians((degreeHour*15 + minute/4 + second/240)); + radian = this.convertToRadians((degreeHour * 1 + minute / 60 + second / 3600)); + radian = isNegativeAngle ? radian * -1 : radian; + } else { + radian = this.convertToRadians((degreeHour * 15 + minute / 4 + second / 240)); } return radian; }