Skip to content
Snippets Groups Projects
Commit 1c897d7a authored by Muthukrishnan's avatar Muthukrishnan
Browse files

TMSS-472 - implemented reservation create

implemented reservation create
parent f7a87395
No related branches found
No related tags found
1 merge request!312Resolve TMSS-472
...@@ -50,6 +50,11 @@ function Jeditor(props) { ...@@ -50,6 +50,11 @@ function Jeditor(props) {
let defKey = refUrl.substring(refUrl.lastIndexOf("/")+1); let defKey = refUrl.substring(refUrl.lastIndexOf("/")+1);
schema.definitions[defKey] = (await $RefParser.resolve(refUrl)).get(newRef); schema.definitions[defKey] = (await $RefParser.resolve(refUrl)).get(newRef);
property["$ref"] = newRef; property["$ref"] = newRef;
if(schema.definitions[defKey].type && schema.definitions[defKey].type === 'array'){
let resolvedItems = await resolveSchema(schema.definitions[defKey].items);
schema.definitions = {...schema.definitions, ...resolvedItems.definitions};
delete resolvedItems['definitions'];
}
} else if(property["type"] === "array") { // reference in array items definition } else if(property["type"] === "array") { // reference in array items definition
let resolvedItems = await resolveSchema(property["items"]); let resolvedItems = await resolveSchema(property["items"]);
schema.definitions = {...schema.definitions, ...resolvedItems.definitions}; schema.definitions = {...schema.definitions, ...resolvedItems.definitions};
......
import {TimelineView} from './view'; import {TimelineView} from './view';
import {WeekTimelineView} from './week.view'; import {WeekTimelineView} from './week.view';
import { ReservationCreate } from './reservation.create';
export {TimelineView, WeekTimelineView} ; export {TimelineView, WeekTimelineView, ReservationCreate} ;
import React, {Component} from 'react';
import { Redirect } from 'react-router-dom';
import _ from 'lodash';
import {Growl} from 'primereact/components/growl/Growl';
import AppLoader from '../../layout/components/AppLoader';
import PageHeader from '../../layout/components/PageHeader';
import UIConstants from '../../utils/ui.constants';
import {Calendar} from 'primereact/calendar';
import { InputMask } from 'primereact/inputmask';
import {Dropdown} from 'primereact/dropdown';
import {InputText} from 'primereact/inputtext';
import {InputTextarea} from 'primereact/inputtextarea';
import { Button } from 'primereact/button';
import {Dialog} from 'primereact/components/dialog/Dialog';
import ProjectService from '../../services/project.service';
import ReservationService from '../../services/reservation.service';
import UnitService from '../../utils/unit.converter';
import Jeditor from '../../components/JSONEditor/JEditor';
/**
* Component to create a new Reservation
*/
export class ReservationCreate extends Component {
constructor(props) {
super(props);
this.state= {
isLoading: true,
redirect: null,
paramsSchema: null, // JSON Schema to be generated from strategy template to pass to JSON editor
dialog: { header: '', detail: ''}, // Dialog properties
errors: {
name: '',
},
touched: {
name: '',
},
reservation: {
name: '',
description: '',
start_time: '',
duration: '',
project: (props.match?props.match.params.project:null) || null,
},
errors: {}, // Validation Errors
validFields: {}, // For Validation
validForm: false, // To enable Save Button
validEditor: false,
durationError: false,
};
this.projects = []; // All projects to load project dropdown
this.stations = [];
this.reasons = [];
this.reservations = [];
// 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: "From Date can not be empty"},
};
this.tooltipOptions = UIConstants.tooltipOptions;
this.setEditorOutput = this.setEditorOutput.bind(this);
this.saveReservation = this.saveReservation.bind(this);
this.reset = this.reset.bind(this);
this.cancelCreate = this.cancelCreate.bind(this);
}
async componentDidMount(){
const promises = [ ProjectService.getProjectList(),
ReservationService.getReservation(),
]
await Promise.all(promises).then(responses => {
this.projects = responses[0];
this.reservations = responses[1];
});
let reservationTemplate = this.reservations.find(reason => reason.name === 'resource reservation');
let schema = {
properties: {}
};
if(reservationTemplate) {
schema = reservationTemplate.schema;
}
this.setState({
paramsSchema: schema,
isLoading: false,
reservationTemplate: reservationTemplate,
});
}
/**
* Function to set form values to the Reservation object
* @param {string} key
* @param {object} value
*/
setReservationParams(key, value) {
this.setState({
touched: {
...this.state.touched,
[key]: true
}
});
let reservation = this.state.reservation;
reservation[key] = value;
this.setState({reservation: reservation, validForm: this.validateForm(key), validEditor: this.validateEditor()});
this.validateEditor();
}
/**
* This function is mainly added for Unit Tests. If this function is removed Unit Tests will fail.
*/
validateEditor() {
return this.validEditor && !this.state.durationError? true : false;
}
/**
* Function to call on change and blur events from input components
* @param {string} key
* @param {any} value
*/
setParams(key, value, type) {
if(key === 'duration' && !this.validateDuration( value)) {
this.setState({
durationError: true
})
return;
}
let reservation = this.state.reservation;
switch(type) {
case 'NUMBER': {
reservation[key] = value?parseInt(value):0;
break;
}
default: {
reservation[key] = value;
break;
}
}
this.setState({reservation: reservation, validForm: this.validateForm(key), durationError: false});
}
validateDuration(duration) {
const splitOutput = duration.split(':');
if (splitOutput.length < 3) {
return false;
} else {
if (parseInt(splitOutput[1])>59 || parseInt(splitOutput[2])>59) {
return false;
}
const timeValue = parseInt(splitOutput[1]*60) + parseInt(splitOutput[2]);
if (timeValue !== 'NaN' && timeValue > 3600) {
return false;
}
}
return 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.
* @param {string} fieldName
*/
validateForm(fieldName) {
let validForm = false;
let errors = this.state.errors;
let validFields = this.state.validFields;
if (fieldName) {
delete errors[fieldName];
delete validFields[fieldName];
if (this.formRules[fieldName]) {
const rule = this.formRules[fieldName];
const fieldValue = this.state.reservation[fieldName];
if (rule.required) {
if (!fieldValue) {
errors[fieldName] = rule.message?rule.message:`${fieldName} is required`;
} else {
validFields[fieldName] = true;
}
}
} else {
}
} else {
errors = {};
validFields = {};
for (const fieldName in this.formRules) {
const rule = this.formRules[fieldName];
const fieldValue = this.state.reservation[fieldName];
if (rule.required) {
if (!fieldValue) {
errors[fieldName] = rule.message?rule.message:`${fieldName} is required`;
} else {
validFields[fieldName] = true;
}
}
}
}
this.setState({errors: errors, validFields: validFields});
if (Object.keys(validFields).length === Object.keys(this.formRules).length) {
validForm = true;
}
return validForm;
}
setEditorOutput(jsonOutput, errors) {
this.paramsOutput = jsonOutput;
this.validEditor = errors.length === 0;
this.setState({ paramsOutput: jsonOutput,
validEditor: errors.length === 0,
validForm: this.validateForm()});
}
saveReservation(){
let reservation = this.state.reservation;
let project = this.projects.find(project => project.name === reservation.project);
reservation['duration'] = ( reservation['duration'] === ''? 0: UnitService.getHHmmssToSecs(reservation['duration']));
reservation['project']= project.url;
reservation['specifications_template']= this.reservations[0].url;
reservation['specifications_doc']= this.paramsOutput;
reservation = ReservationService.saveReservation(reservation);
if (reservation && reservation !== null){
const dialog = {header: 'Success', detail: 'Reservation is created successfully. Do you want to create another Reservation?'};
this.setState({ dialogVisible: true, dialog: dialog})
} else {
this.growl.show({severity: 'error', summary: 'Error Occured', detail: 'Unable to save Reservation'});
}
}
/**
* Reset function to be called when user wants to create new Reservation
*/
reset() {
this.setState({
dialogVisible: false,
dialog: { header: '', detail: ''},
errors: [],
reservation: {
name: '',
description: '',
start_time: '',
duration: '',
project: '',
},
paramsSchema: this.state.paramsSchema,
paramsOutput: null,
validEditor: false,
validFields: {},
touched:false,
stationGroup: []
});
}
/**
* Cancel Reservation creation and redirect
*/
cancelCreate() {
this.props.history.goBack();
}
render() {
if (this.state.redirect) {
return <Redirect to={ {pathname: this.state.redirect} }></Redirect>
}
const schema = this.state.paramsSchema;
let jeditor = null;
if (schema) {
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',link: this.props.history.goBack,title:'Click to close Reservation creation', props : { pathname: `/su/timelineview`}}]}/>
{ 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 htmlFor="reservationName" className="col-lg-2 col-md-2 col-sm-12">From Date <span style={{color:'red'}}>*</span></label>
<div className="col-lg-3 col-md-3 col-sm-12">
<Calendar
d dateFormat="dd-M-yy"
value= {this.state.reservation.start_time}
onChange= {e => this.setParams('start_time',e.value)}
onBlur= {e => this.setParams('start_time',e.value)}
data-testid="start_time"
tooltip="Moment at which the reservation starts from, that is, when its reservation can run." tooltipOptions={this.tooltipOptions}
showIcon={true}
showTime= {true}
showSeconds= {true}
hourFormat= "24"
/>
<label className={this.state.errors.from?"error":"info"}>
{this.state.errors.start_time ? this.state.errors.start_time : ""}
</label>
</div>
<div className="col-lg-1 col-md-1 col-sm-12"></div>
<label htmlFor="duration" className="col-lg-2 col-md-2 col-sm-12">Duration </label>
<div className="col-lg-3 col-md-3 col-sm-12" data-testid="duration" >
<InputMask
value={this.state.reservation.duration}
mask="99:99:99"
placeholder="HH:mm:ss"
className="inputmask"
onChange= {e => this.setParams('duration',e.value)}
ref={input =>{this.input = input}}
/>
<label className="error">
{this.state.durationError ? 'Invalid duration' : ""}
</label>
</div>
</div>
<div className="p-field p-grid">
<label htmlFor="project" className="col-lg-2 col-md-2 col-sm-12">Project <span style={{color:'red'}}>*</span></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"}>
{(this.state.errors.project && this.state.touched.project) ? this.state.errors.project : "Select Project"}
</label>
</div>
</div>
<div className="p-grid">
<div className="p-col-12">
{this.state.paramsSchema?jeditor:""}
</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}
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.cancelCreate} />
</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: `/su/timelineview`});}} 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>
</div>
</React.Fragment>
);
}
}
export default ReservationCreate;
\ No newline at end of file
...@@ -326,7 +326,10 @@ export class TimelineView extends Component { ...@@ -326,7 +326,10 @@ export class TimelineView extends Component {
return ( return (
<React.Fragment> <React.Fragment>
<PageHeader location={this.props.location} title={'Scheduling Units - Timeline View'} <PageHeader location={this.props.location} title={'Scheduling Units - Timeline View'}
actions={[{icon: 'fa-calendar-alt',title:'Week View', props : { pathname: `/su/timelineview/week`}}]}/> actions={[
{icon: 'fa-plus-square',title:'Add Reservation', props : { pathname: `/su/timelineview/reservation/create`}},
{icon: 'fa-calendar-alt',title:'Week View', props : { pathname: `/su/timelineview/week`}},
]}/>
{ this.state.isLoading ? <AppLoader /> : { this.state.isLoading ? <AppLoader /> :
<div className="p-grid"> <div className="p-grid">
{/* SU List Panel */} {/* SU List Panel */}
......
...@@ -312,7 +312,9 @@ export class WeekTimelineView extends Component { ...@@ -312,7 +312,9 @@ export class WeekTimelineView extends Component {
return ( return (
<React.Fragment> <React.Fragment>
<PageHeader location={this.props.location} title={'Scheduling Units - Week View'} <PageHeader location={this.props.location} title={'Scheduling Units - Week View'}
actions={[{icon: 'fa-clock',title:'View Timeline', props : { pathname: `/su/timelineview`}}]}/> actions={[
{icon: 'fa-plus-square',title:'Add Reservation', props : { pathname: `/su/timelineview/reservation/create`}},
{icon: 'fa-clock',title:'View Timeline', props : { pathname: `/su/timelineview`}}]}/>
{ this.state.isLoading ? <AppLoader /> : { this.state.isLoading ? <AppLoader /> :
<> <>
{/* <div className="p-field p-grid"> {/* <div className="p-field p-grid">
......
...@@ -14,7 +14,7 @@ import ViewSchedulingUnit from './Scheduling/ViewSchedulingUnit' ...@@ -14,7 +14,7 @@ import ViewSchedulingUnit from './Scheduling/ViewSchedulingUnit'
import SchedulingUnitCreate from './Scheduling/create'; import SchedulingUnitCreate from './Scheduling/create';
import EditSchedulingUnit from './Scheduling/edit'; import EditSchedulingUnit from './Scheduling/edit';
import { CycleList, CycleCreate, CycleView, CycleEdit } from './Cycle'; import { CycleList, CycleCreate, CycleView, CycleEdit } from './Cycle';
import {TimelineView, WeekTimelineView} from './Timeline'; import {TimelineView, WeekTimelineView, ReservationCreate} from './Timeline';
import SchedulingSetCreate from './Scheduling/create.scheduleset'; import SchedulingSetCreate from './Scheduling/create.scheduleset';
import Workflow from './Workflow'; import Workflow from './Workflow';
...@@ -159,7 +159,12 @@ export const routes = [ ...@@ -159,7 +159,12 @@ export const routes = [
name: 'Workflow', name: 'Workflow',
title: 'QA Reporting (TO)' title: 'QA Reporting (TO)'
}, },
{
path: "/su/timelineview/reservation/create",
component: ReservationCreate,
name: 'Reservation Add',
title: 'Reservation - Add'
}
]; ];
export const RoutedContent = () => { export const RoutedContent = () => {
......
const axios = require('axios');
//axios.defaults.baseURL = 'http://192.168.99.100:8008/api';
axios.defaults.headers.common['Authorization'] = 'Basic dGVzdDp0ZXN0';
const ReservationService = {
getReservation: async function () {
try {
const url = `/api/reservation_template`;
const response = await axios.get(url);
return response.data.results;
} catch (error) {
console.error(error);
}
},
saveReservation: async function (reservation) {
try {
const response = await axios.post(('/api/reservation/'), reservation);
return response.data;
} catch (error) {
console.error(error);
return null;
}
}
}
export default ReservationService;
import _ from 'lodash';
const UnitConverter = { const UnitConverter = {
resourceUnitMap: {'time':{display: 'Hours', conversionFactor: 3600, mode:'decimal', minFractionDigits:0, maxFractionDigits: 2 }, 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}, 'bytes': {display: 'TB', conversionFactor: (1024*1024*1024*1024), mode:'decimal', minFractionDigits:0, maxFractionDigits: 3},
...@@ -29,6 +31,13 @@ const UnitConverter = { ...@@ -29,6 +31,13 @@ const UnitConverter = {
} }
return seconds; return seconds;
}, },
getHHmmssToSecs: function(seconds) {
if (seconds) {
const strSeconds = _.split(seconds, ":");
return strSeconds[0]*3600 + strSeconds[1]*60 + Number(strSeconds[2]);
}
return 0;
},
radiansToDegree: function(object) { radiansToDegree: function(object) {
for(let type in object) { for(let type in object) {
if (type === 'transit_offset') { if (type === 'transit_offset') {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment