diff --git a/SAS/TMSS/frontend/tmss_webapp/src/App.css b/SAS/TMSS/frontend/tmss_webapp/src/App.css index 04acfc474c1fa3506e186746f08e0a62199407ef..c5efdaf30ebc41f4e00aae5a5aa268579147d2eb 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/App.css +++ b/SAS/TMSS/frontend/tmss_webapp/src/App.css @@ -33,6 +33,7 @@ label { font-size: 14px; font-weight: 600; align-items: flex-start !important; + color: #004B93; } .chips-readonly > ul { @@ -53,6 +54,15 @@ p { .card-body { padding: 0.25rem; + margin-bottom: 5px !important; +} + +.form-group { + margin-bottom: 5px !important; +} + +.btn-group { + margin-bottom: 5px; } .task-list { @@ -71,6 +81,25 @@ p { #editor_holder label { text-transform: capitalize; + color: #28289b; +} + +.fa { + color: #005b9f; +} + +thead { + background-color: #ebeaea; +} + +.error { + color: #dc3545; + font-size: 80%; + font-weight: 400; +} + +.input-error { + border-color: #dc3545 !important; } @keyframes App-logo-spin { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js b/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js index dbba73d6364eb934f67e623ddca26b392f8f658b..6ca2307f24eb39c0625632cd0454f163db40242b 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js @@ -6,17 +6,12 @@ import React, {useEffect, useRef} from 'react'; import _ from 'lodash'; import flatpickr from 'flatpickr'; -// Sample JSON schema. The same can be received from an API call -// import SchemaDB from './task.observation.schema'; - import "@fortawesome/fontawesome-free/css/all.css"; import "flatpickr/dist/flatpickr.css"; const JSONEditor = require("@json-editor/json-editor").JSONEditor; function Jeditor(props) { // console.log("In JEditor"); - // State object to hold the editor reference to get output - // const [stateEditor, setStateEditor] = useState({editor:null}); const editorRef = useRef(null); let editor = null; useEffect(() => { @@ -24,6 +19,7 @@ function Jeditor(props) { let schema = {}; Object.assign(schema, props.schema?props.schema:{}); + // Customize the pointing property to capture angle1 and angle2 to in specified format for (const definitionKey in schema.definitions) { if (definitionKey === 'pointing') { const defintion = schema.definitions[definitionKey]; @@ -44,6 +40,7 @@ function Jeditor(props) { } } + // Customize datatype of certain properties like subbands, duration, etc., getCustomProperties(schema.properties); schema.title = props.title; @@ -80,38 +77,31 @@ function Jeditor(props) { return errors; }); schema.format = "grid" - console.log(schema); const editorOptions = { form_name_root: "specification", schema: schema, - // display_required_only: true, // circular references will blow up without this theme: 'bootstrap4', iconlib: 'fontawesome5', // theme: 'tailwind', // iconlib: 'spectre', display_required_only: false, - // startval: props.initValue, remove_button_labels: true, disable_edit_json: true, disable_properties: true, disable_collapse: true, compact: true }; + // Set Initial value to the editor if (props.initValue) { editorOptions.startval = updateInput(_.cloneDeep(props.initValue)); } editor = new JSONEditor(element, editorOptions); - // editor.setValue(props.initValue); // editor.getEditor('root').disable(); if (props.disabled) { editor.disable(); } - if (!props.initValue) { - // editor.setValue(updateInput(editor.getValue())); - } - // setStateEditor({editor: editor}); - if (props.parentFunc) { - props.parentFunc(childFunc); + if (props.parentFunction) { + props.parentFunction(editorFunction); } editorRef.current = editor; editor.on('change', () => {setEditorOutput()}); @@ -130,10 +120,18 @@ function Jeditor(props) { } } - function childFunc() { + /** + * Function called by the parent component to perform certain action ib JEditor + */ + function editorFunction() { editorRef.current.destroy(); } + /** + * Function to convert the angle fields in HH:mm:ss or DD:mm:ss format based on isDegree value. + * @param {Object} defProperty + * @param {Boolean} isDegree + */ function getAngleProperty(defProperty, isDegree) { /*let newProperty = { "type": "object", @@ -181,8 +179,8 @@ function Jeditor(props) { description: (defProperty.description + (isDegree?'(Degrees:Minutes:Seconds)':'(Hours:Minutes:Seconds)')), default: "00:00:00", validationType: isDegree?'angle':'time', - "options": { - // "grid_columns": 4, + options: { + "grid_columns": 4, "inputAttributes": { "placeholder": isDegree?"DD:mm:ss":"HH:mm:ss" }, @@ -196,6 +194,10 @@ function Jeditor(props) { return newProperty; } + /** + * Function to get the schema change for specified properties like subbands, duration, column width, etc + * @param {Object} properties + */ function getCustomProperties(properties) { for (const propertyKey in properties) { const propertyValue = properties[propertyKey]; @@ -212,13 +214,13 @@ function Jeditor(props) { // }; properties[propertyKey] = newProperty; } else if (propertyKey.toLowerCase() === 'duration') { - propertyValue.title = "Duration (minutes)"; + /*propertyValue.title = "Duration (minutes)"; propertyValue.default = "1"; propertyValue.description = "Duration of this observation. Enter in decimal for seconds. For example 0.5 for 30 seconds"; propertyValue.minimum = 0.25; propertyValue.options = { grid_columns: 6 - }; + };*/ /*propertyValue.title = "Duration"; propertyValue.default = "1H20M30S"; propertyValue.type = "string"; @@ -240,13 +242,13 @@ function Jeditor(props) { } } }*/ - /*let newProperty = { + let newProperty = { "type": "string", "format": "time", "title": "Duration", "description": `${propertyValue.description} (Hours:Minutes:Seconds)`, "options": { - "grid_columns": 4, + "grid_columns": 3, "inputAttributes": { "placeholder": "Enter time" }, @@ -265,9 +267,9 @@ function Jeditor(props) { "allowInput": true } } - };*/ + }; - // properties[propertyKey] = newProperty; + properties[propertyKey] = newProperty; } else if (propertyValue instanceof Object) { if (propertyKey !== 'properties' && propertyKey !== 'default') { propertyValue.format = "grid"; @@ -279,16 +281,20 @@ function Jeditor(props) { } else if (propertyKey === 'QA' || propertyKey === 'beams') { propertyValue.propertyOrder = 10000; } - if (propertyKey === 'storage_cluster') { - propertyValue.options = { - grid_columns: 6 - }; + if (propertyKey === 'storage_cluster' || propertyKey === 'integration_time') { + let options = propertyValue.options?propertyValue.options:{}; + options.grid_columns = 3; + propertyValue.options = options; } getCustomProperties(propertyValue); } } } + /** + * Function to format the input for custom fields when the editor receive the inital values from the parent component + * @param {*} editorInput + */ function updateInput(editorInput) { for (const inputKey in editorInput) { const inputValue = editorInput[inputKey]; @@ -302,12 +308,17 @@ function Jeditor(props) { updateInput(inputValue); } } else if (inputKey.toLowerCase() === 'duration') { - editorInput[inputKey] = inputValue/60; + // editorInput[inputKey] = inputValue/60; + editorInput[inputKey] = getTimeInput(inputValue); } } return editorInput; } + /** + * Function to format the output of the customized fields + * @param {*} editorOutput + */ function updateOutput(editorOutput) { for (const outputKey in editorOutput) { let outputValue = editorOutput[outputKey]; @@ -321,12 +332,19 @@ function Jeditor(props) { } else if (outputKey === 'subbands') { editorOutput[outputKey] = getSubbandOutput(outputValue); } else if (outputKey.toLowerCase() === 'duration') { - editorOutput[outputKey] = outputValue * 60; + // editorOutput[outputKey] = outputValue * 60; + const splitOutput = outputValue.split(':'); + editorOutput[outputKey] = (splitOutput[0] * 3600 + splitOutput[1] * 60 + splitOutput[2]*1); } } return editorOutput; } + /** + * Function to format angle values in the input of inital values + * @param {*} prpInput + * @param {Boolean} isDegree + */ function getAngleInput(prpInput, isDegree) { const degrees = prpInput * 180 / Math.PI; if (isDegree) { @@ -352,6 +370,10 @@ function Jeditor(props) { } } + /** + * Function to format subband list inout arrived as Array to String + * @param {Array} prpInput + */ function getSubbandInput(prpInput) { let subbandString = ""; for (let index=0; index < prpInput.length; index++) { @@ -374,6 +396,22 @@ function Jeditor(props) { return subbandString; } + /** + * Convert time value in seconds to string format of HH:mm:ss + * @param {Number} seconds + */ + function getTimeInput(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}`); + } + + /** + * Converts the angle input to radians + * @param {String} prpOutput + * @param {Boolean} isDegree + */ function getAngleOutput(prpOutput, isDegree) { /*if ('dd' in prpOutput) { return ((prpOutput.dd + prpOutput.mm/60 + prpOutput.ss/3600)*Math.PI/180); @@ -388,6 +426,10 @@ function Jeditor(props) { } } + /** + * Validate time entered as string in HH:mm:ss format + * @param {String} prpOutput + */ function validateTime(prpOutput) { const splitOutput = prpOutput.split(':'); if (splitOutput.length < 3) { @@ -401,6 +443,10 @@ function Jeditor(props) { return true; } + /** + * Validate angle input to not exceed 90 degrees + * @param {String} prpOutput + */ function validateAngle(prpOutput) { const splitOutput = prpOutput.split(':'); if (splitOutput.length < 3) { @@ -414,6 +460,10 @@ function Jeditor(props) { return true; } + /** + * Validates if the subband list custom field + * @param {String} prpOutput + */ function validateSubbandOutput(prpOutput){ try { if (prpOutput) { @@ -446,23 +496,22 @@ function Jeditor(props) { return true; } + /** + * Convert the string input for subband list to Array + * @param {String} prpOutput + */ function getSubbandOutput(prpOutput) { - // if (validateSubbandOutput(prpOutput)) { - const subbandArray = prpOutput.split(","); - let subbandList = []; - for (const subband of subbandArray ) { - const subbandRange = subband.split('..'); - if (subbandRange.length > 1) { - subbandList = subbandList.concat( _.range(subbandRange[0], (parseInt(subbandRange[1])+1))); - } else { - subbandList = subbandList.concat(parseInt(subbandRange[0])); - } + const subbandArray = prpOutput.split(","); + let subbandList = []; + for (const subband of subbandArray ) { + const subbandRange = subband.split('..'); + if (subbandRange.length > 1) { + subbandList = subbandList.concat( _.range(subbandRange[0], (parseInt(subbandRange[1])+1))); + } else { + subbandList = subbandList.concat(parseInt(subbandRange[0])); } - prpOutput = subbandList; - // } else { - // alert("Subband list values are not valid!"); - // prpOutput = []; - // } + } + prpOutput = subbandList; return prpOutput; } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js index ce2cf71aad6460a583b1a319ac67bbd7b63b569f..15496a2ba20b1ff13e8e1c7ff6b2bb406b037599 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js @@ -3,8 +3,6 @@ import styled from 'styled-components' import { useSortBy, useTable, useFilters, useGlobalFilter, useAsyncDebounce } from 'react-table' import matchSorter from 'match-sorter' import _ from 'lodash'; -import {Button} from 'react-bootstrap' -import { DropdownMenu, } from './ActionComponents'; import moment from 'moment'; import { useHistory } from "react-router-dom"; import {OverlayPanel} from 'primereact/overlaypanel'; @@ -44,14 +42,14 @@ function GlobalFilter({ return ( <span> - Table Search:{' '} + <input value={value || ""} onChange={e => { setValue(e.target.value); onChange(e.target.value); }} - /> + /> {" "}<i className="fa fa-search"></i> </span> ) } @@ -160,15 +158,13 @@ function Table({ columns, data }) { <> <div id="block_container" style={{ display: 'flex', verticalAlign: 'middle', marginTop:'20px'}}> <div style={{textAlign:'left', marginRight:'30px'}}> - {/* <Button className="p-link" type="button" label="Toggle Columns" onClick={(e) => op.current.toggle(e)} style={{marginLeft:'30px'}}> */} - <i className="pi pi-bars col-filter-btn" label="Toggle Columns" onClick={(e) => op.current.toggle(e)} /> - {/* </Button> */} + <i className="fa fa-columns col-filter-btn" label="Toggle Columns" onClick={(e) => op.current.toggle(e)} /> <OverlayPanel ref={op} id="overlay_panel" showCloseIcon={false} > <div> - <div style={{textAlign: 'center'}}> - <label>Select column(s) to view</label> - </div> - <div style={{float: 'left', backgroundColor: '#d1cdd936', width: '250px', height: '400px', overflow: 'auto', marginBottom:'10px', padding:'5px'}}> + <div style={{textAlign: 'center'}}> + <label>Select column(s) to view</label> + </div> + <div style={{float: 'left', backgroundColor: '#d1cdd936', width: '250px', height: '400px', overflow: 'auto', marginBottom:'10px', padding:'5px'}}> <div id="tagleid" > <div > <div style={{marginBottom:'5px'}}> @@ -176,9 +172,7 @@ function Table({ columns, data }) { </div> {allColumns.map(column => ( <div key={column.id}> - {/* <label style={{color:'grey'}}> */} <input type="checkbox" {...column.getToggleHiddenProps()} />{' '} {_.startCase(column.id)} - {/* </label> */} </div> ))} <br /> @@ -194,7 +188,6 @@ function Table({ columns, data }) { preGlobalFilteredRows={preGlobalFilteredRows} globalFilter={state.globalFilter} setGlobalFilter={setGlobalFilter} - /> } </div> @@ -252,21 +245,26 @@ function filterGreaterThan(rows, id, filterValue) { filterGreaterThan.autoRemove = val => typeof val !== 'number' function ViewTable(props) { - const history = useHistory(); - const navigateTo = (pdata) => history.push({ - pathname: props.path, - state: { - "scheunit": props.data, - "schedule_task":props.data1, - "defaultcolumns": props.defaultcolumns, - } - }); + const history = useHistory(); + // Data to show in table tbldata = props.data; + // Default Header to show in table and other columns header will not show until user action on UI let defaultheader = props.defaultcolumns; let columns = []; let defaultdataheader = Object.keys(defaultheader[0]); - + if(props.showaction === 'true'){ + columns.push({ + Header: 'Action', + id:'action', + accessor: 'id', + Cell: props => <button className='p-link' onClick={navigateTo(props.value)} ><i className="fa fa-edit" style={{cursor: 'pointer'}}></i></button>, + disableFilters: true, + disableSortBy: true, + isVisible: defaultdataheader.includes('id'), + }) + } + defaultdataheader.forEach(header =>{ columns.push({ Header: defaultheader[0][header], @@ -281,7 +279,7 @@ function ViewTable(props) { dataheader = Object.keys(tbldata[0]); dataheader.forEach(header => { if(!defaultdataheader.includes(header)){ - var text = _.startCase(header); + var text = _.startCase(header); columns.push({ Header: text, accessor: header, @@ -309,19 +307,27 @@ function ViewTable(props) { return date; } - if(columns.length>0 && props.showaction === 'true'){ - columns.push( - { - Header: 'Action', - Cell: row => - <a onClick={navigateTo} href ><i className="pi pi-pencil" style={{cursor: 'pointer'}}></i></a>, + // if(columns.length>0 && props.showaction === 'true'){ + // columns.push( + // { + // Header: 'Action', + // Cell: row => + // <a onClick={navigateTo} href ><i className="pi pi-pencil" style={{cursor: 'pointer'}}></i></a>, + const navigateTo = (id) => () => { + Object.entries(props.paths[0]).map(([key,value]) =>{ + return history.push({ + pathname: value, + state: { + "id": id, + } }) - } + }) + } return ( <div > - <Table columns={columns} data={tbldata} /> + <Table columns={columns} data={tbldata} className="-striped -highlight" /> </div> ) diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js index ab95353e3800a896612c766a447bed8522ef5940..d7b118a645897188244666a1ac12206c5bf64ba9 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js @@ -1,30 +1,55 @@ import React, { Component } from 'react' import 'primeflex/primeflex.css'; import ViewTable from './../../components/ViewTable'; +import {getScheduling_Unit_Draft} from '../../services/ScheduleService' class SchedulingUnitList extends Component{ + constructor(props){ + super(props) + this.state = { + scheduleunit: [], + paths: [{ + "View": "/schedulingunit/view", + }] + } + } + + componentDidMount(){ + getScheduling_Unit_Draft().then(scheduleunit =>{ + this.setState({ + scheduleunit : scheduleunit.data + }); + }) + } + render(){ - let scheduleunit = this.props.scheunit; - {this.props.scheunit && - scheduleunit.forEach(item =>{ + + if(this.state.scheduleunit.results){ + this.state.scheduleunit.results.forEach(item =>{ delete item['requirements_doc'] }) } - // Default column for Schedule Unit + // The default table column value and header to show in UI // let defaultcolumns = [ {"name":"Name","description":"Description","created_at":"Created Date","updated_at":"Updated Date","requirements_template_id": "Requirement Temp","scheduling_set_id":" Scheduling Unit"}] let defaultcolumns = [ {"name":"Name","description":"Description","created_at":"Created Date","updated_at":"Updated Date","requirements_template_id": "Template"}] return( <> - {scheduleunit && + {/* + * Call View table to show table data, the parameters are, + data - Pass API data + defaultcolumns - This colum will be populate by default in table with header mentioned + showaction - {true/false} -> to show the action column + paths - specify the path for navigation - Table will set "id" value for each row in action button + */} + {this.state.scheduleunit.results && <ViewTable - data={scheduleunit} - data1={this.props.schedule_task} + data={this.state.scheduleunit.results} defaultcolumns={defaultcolumns} showaction="true" - path="/viewschedulingunit" + paths={this.state.paths} /> - } + } </> ) } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js index 1a05bf12201d65164099bf952c67695d303c3ac5..b890b01651899dc41059e2b3b7d593f433788b53 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js @@ -3,14 +3,42 @@ import {Link} from 'react-router-dom' import 'primeflex/primeflex.css'; import { Chips } from 'primereact/chips'; import ViewTable from './../../components/ViewTable'; +import {getScheduling_Unit_Draft_By_Id, getTasks_Draft_By_scheduling_Unit_Id} from '../../services/ScheduleService' class ViewSchedulingUnit extends Component{ + constructor(props){ + super(props) + this.state = { + scheduleunit: null, + schedule_unit_task: [], + paths: [{ + "View": "/task", + }] + } + } + + componentDidMount(){ + let schedule_id = this.props.location.state.id + if (schedule_id) { + getScheduling_Unit_Draft_By_Id(schedule_id).then(scheduleunit =>{ + getTasks_Draft_By_scheduling_Unit_Id(scheduleunit.data.id).then(tasks =>{ + this.setState({ + scheduleunit : scheduleunit.data,schedule_unit_task : tasks.data.results + }); + }) + }) + } + } + render(){ - let tasks_details = this.props.location.state.schedule_task; - tasks_details.forEach(item =>{ - delete item['specifications_doc'] - }) + + if (this.state.schedule_unit_task.length>0) { + this.state.schedule_unit_task.forEach(item =>{ + delete item['specifications_doc'] + }); + } + // Default column for Schedule Unit-Task Details // let defaultcolumns = [ {"id":"Task Identifier","name":"Task Name","description":"Task Description","created_at":"Created Date","updated_at":"Updated Date","requirements_template_id": "Requirement Temp"}] @@ -22,42 +50,49 @@ class ViewSchedulingUnit extends Component{ <h2>Scheduling Unit - Details </h2> </div> <div className="p-col-1"> - <Link to={{ pathname: '/sheduling/edit', state: {id: this.props.location.state.scheunit?this.props.location.state.scheunit[0].id:''}}} tooltip="Edit Task" > - <i className="pi pi-pencil" style={{marginTop: "10px"}}></i> + <Link to={{ pathname: '/sheduling/edit', state: {id: this.state.scheduleunit?this.state.scheduleunit.id:''}}} tooltip="Edit" > + <i className="fa fa-edit" style={{marginTop: "10px"}}></i> </Link> </div> </div> - {this.props.location.state.scheunit && + {this.state.scheduleunit && <> <div className="p-grid"> <label className="p-col-2">Name</label> - <span className="p-col-4">{this.props.location.state.scheunit[0].name}</span> + <span className="p-col-4">{this.state.scheduleunit.name}</span> <label className="p-col-2">Description</label> - <span className="p-col-4">{this.props.location.state.scheunit[0].description}</span> + <span className="p-col-4">{this.state.scheduleunit.description}</span> </div> <div className="p-grid"> <label className="p-col-2">Created At</label> - <span className="p-col-4">{this.props.location.state.scheunit[0].created_at}</span> + <span className="p-col-4">{this.state.scheduleunit.created_at}</span> <label className="p-col-2">Updated At</label> - <span className="p-col-4">{this.props.location.state.scheunit[0].updated_at}</span> + <span className="p-col-4">{this.state.scheduleunit.updated_at}</span> </div> <div className="p-grid"> <label className="p-col-2">Tags</label> - <Chips className="p-col-4 chips-readonly" disabled value={this.props.location.state.scheunit[0].tags}></Chips> - <span className="p-col-4">{this.props.location.state.scheunit[0].tags}</span> + <Chips className="p-col-4 chips-readonly" disabled value={this.state.scheduleunit.tags}></Chips> + <span className="p-col-4">{this.state.scheduleunit.tags}</span> </div> </> } <div style={{marginTop: '20px'}}> <h3>Tasks Details</h3> </div> - {this.props.location.state.schedule_task && + {/* + * Call View table to show table data, the parameters are, + data - Pass API data + defaultcolumns - This colum will be populate by default in table with header mentioned + showaction - {true/false} -> to show the action column + paths - specify the path for navigation - Table will set "id" value for each row in action button + */} + {this.state.schedule_unit_task.length>0 && <ViewTable - data={tasks_details} + data={this.state.schedule_unit_task} defaultcolumns={defaultcolumns} showaction="true" - path="/task" + paths={this.state.paths} /> } </> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/index.js index 17a481cf2b885dcc2e56c5b251d5960a017947ee..886fd7a544c90aafa55362200812dca83129a145 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/index.js @@ -1,6 +1,5 @@ import React, {Component} from 'react'; import SchedulingUnitList from './SchedulingUnitList' -import {getScheduling_Unit_Draft, getSchedule_Unit_Draft_By_Id} from '../../services/ScheduleService' export class Scheduling extends Component { constructor(props){ @@ -10,23 +9,13 @@ export class Scheduling extends Component { schedule_unit_task: [] } } - - componentDidMount(){ - getScheduling_Unit_Draft() - .then(scheduleunit => { - getSchedule_Unit_Draft_By_Id(scheduleunit.data.results[0].id) - .then(tasks => { - this.setState({scheduleunit: scheduleunit.data, schedule_unit_task: tasks.data }); - }); - }); - } - + render() { return ( <> - <h2>Scheduling Units List</h2> + <h2>Scheduling Unit - List</h2> {this.state.scheduleunit && - <SchedulingUnitList scheunit= {this.state.scheduleunit.results} schedule_task={this.state.schedule_unit_task.results} /> + <SchedulingUnitList /> } </> ); diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/edit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/edit.js index 48edd24f644a2cb789d5614e2ef02a47cafd6c57..11792bcb9a99ba51b0454b7317b97bba9cb2afe8 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/edit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/edit.js @@ -1,13 +1,10 @@ import React, {Component} from 'react'; import { Link, Redirect } from 'react-router-dom'; -import moment from 'moment'; import _ from 'lodash'; import {InputText} from 'primereact/inputtext'; import {InputTextarea} from 'primereact/inputtextarea'; -import { Calendar } from 'primereact/calendar'; import {Chips} from 'primereact/chips'; -import {Checkbox} from 'primereact/checkbox'; import {Dropdown} from 'primereact/dropdown'; import { Button } from 'primereact/button'; @@ -16,7 +13,7 @@ import Jeditor from '../../components/JSONEditor/JEditor'; import TaskService from '../../services/task.services'; export class TaskEdit extends Component { - templateOutput = {}; + templateOutput = {}; // id: selectedTemplateId, output: values enetered in the editor form constructor(props) { super(props); @@ -30,36 +27,61 @@ export class TaskEdit extends Component { }, redirect: null, taskTemplates:[], - validEditor: false - // templateOutput:{}, // id: selectedTemplateId, output: values enetered in the editor form + validEditor: false, + validForm: false, + errors: {} + }; + this.formRules = { + name: {required: true, message: "Name can not be empty"}, + description: {required: true, message: "Description can not be empty"} }; this.setEditorOutput = this.setEditorOutput.bind(this); this.setTaskParams = this.setTaskParams.bind(this); this.changeTaskTemplate = this.changeTaskTemplate.bind(this); - this.setEditorFunc = this.setEditorFunc.bind(this); + this.setEditorFunction = this.setEditorFunction.bind(this); + this.validateForm = this.validateForm.bind(this); this.saveTask = this.saveTask.bind(this); this.cancelEdit = this.cancelEdit.bind(this); } + /** + * This is the callback method to be passed to the JSON editor. + * JEditor will call this function when there is change in the editor. + * @param {Object} jsonOutput + * @param {Array} errors + */ setEditorOutput(jsonOutput, errors) { this.templateOutput[this.state.task.specifications_template_id] = jsonOutput; if (errors.length === 0 && !this.state.validEditor) { - this.setState({validEditor: true}); + this.setState({validEditor: true, validForm: this.validateForm()}); } else if (errors.length > 0 && this.state.validEditor) { - this.setState({validEditor: false}); + this.setState({validEditor: false, validForm: this.validateForm()}); } } + /** + * Function called when there is change in the task parameters. + * @param {String} key + * @param {*} value + */ setTaskParams(key, value) { let task = this.state.task; task[key] = value; - this.setState({task: task}); + this.setState({task: task, validForm: this.validateForm()}); } - setEditorFunc(editorFunc) { - this.setState({editorFunc: editorFunc}); + /** + * JEditor's function that to be called when parent wants to trigger change in the JSON Editor + * @param {Function} editorFunction + */ + setEditorFunction(editorFunction) { + this.setState({editorFunction: editorFunction}); } + /** + * Function to be called when the template schema is changed + * @param {Number} templateId + */ changeTaskTemplate(templateId) { const template = _.find(this.state.taskTemplates, {'id': templateId}); let task = this.state.task; @@ -67,9 +89,34 @@ export class TaskEdit extends Component { task.specifications_template = template.url; this.setState({taskSchema: null}); this.setState({task: task, taskSchema: template.schema}); - this.state.editorFunc(); + this.state.editorFunction(); } + /** + * Function to validate the form excluding the JSON Editor values + */ + validateForm() { + let validForm = false; + let errors = {}; + for (const fieldName in this.formRules) { + const rule = this.formRules[fieldName]; + const fieldValue = this.state.task[fieldName]; + if (rule.required) { + if (!fieldValue) { + errors[fieldName] = rule.message?rule.message:`${fieldName} is required`; + } + } + } + this.setState({errors: errors}); + if (this.state.task.name && this.state.task.description) { + validForm = true; + } + return validForm; + } + + /** + * Function to call the servie and pass the values to save + */ saveTask() { let task = this.state.task; task.specifications_doc = this.templateOutput[task.specifications_template_id]; @@ -110,20 +157,18 @@ export class TaskEdit extends Component { render() { if (this.state.redirect) { return <Redirect to={ {pathname: this.state.redirect, - state: {taskId: this.state.task.id}} }></Redirect> + state: {id: this.state.task.id}} }></Redirect> } const taskSchema = this.state.taskSchema; - let jeditor, created_at, updated_at = null; + let jeditor = null; if (this.state.taskSchema) { jeditor = React.createElement(Jeditor, {title: "Specification", schema: taskSchema, //initValue: this.state.templateOutput[this.state.task.specifications_template_id], initValue: this.templateOutput[this.state.task.specifications_template_id], callback: this.setEditorOutput, - parentFunc: this.setEditorFunc + parentFunction: this.setEditorFunction }); - created_at = moment(this.state.task.created_at).toDate(); - updated_at = moment(this.state.task.created_at).toDate(); } return ( @@ -133,21 +178,27 @@ export class TaskEdit extends Component { <h2>Task - Edit</h2> </div> <div className="p-col-1"> - <Link to={{ pathname: '/task', state: {taskId: this.state.task?this.state.task.id:''}}} tooltip="Close Edit" > - <i className="pi pi-times" style={{marginTop: "10px"}}></i> + <Link to={{ pathname: '/task', state: {id: this.state.task?this.state.task.id:''}}} tooltip="Close Edit" > + <i className="fa fa-window-close" style={{marginTop: "10px"}}></i> </Link> </div> </div> - <div> + <div> <div className="p-fluid"> <div className="p-field p-grid"> - <label htmlFor="taskName" className="p-col-2">Name</label> + <label htmlFor="taskName" className="p-col-2">Name <span style={{color:'red'}}>*</span></label> <div className="p-col-4"> - <InputText id="taskName" type="text" value={this.state.task.name} onChange={(e) => this.setTaskParams('name', e.target.value)}/> + <InputText className={this.state.errors.name ?'input-error':''} id="taskName" type="text" value={this.state.task.name} onChange={(e) => this.setTaskParams('name', e.target.value)}/> + <label className="error"> + {this.state.errors.name ? this.state.errors.name : ""} + </label> </div> - <label htmlFor="description" className="p-col-2">Description</label> + <label htmlFor="description" className="p-col-2">Description <span style={{color:'red'}}>*</span></label> <div className="p-col-4"> - <InputTextarea rows={3} cols={30} value={this.state.task.description} onChange={(e) => this.setTaskParams('description', e.target.value)}/> + <InputTextarea className={this.state.errors.description ?'input-error':''} rows={3} cols={30} value={this.state.task.description} onChange={(e) => this.setTaskParams('description', e.target.value)}/> + <label className="error"> + {this.state.errors.description ? this.state.errors.description : ""} + </label> </div> </div> {/* <div className="p-field p-grid"> @@ -169,8 +220,12 @@ export class TaskEdit extends Component { <div className="p-col-4"> <Checkbox onChange={e => this.setTaskParams('do_cancel', e.checked)} checked={this.state.task.do_cancel}></Checkbox> </div> */} - <label className="p-col-2">Scheduling Unit</label> - <Link className="p-col-4" to={ { pathname:'/scheduling'}}>{this.state.schedulingUnit?this.state.schedulingUnit.name:''}</Link> + {this.state.schedulingUnit && + <> + <label className="p-col-2">Scheduling Unit</label> + <Link className="p-col-4" to={ { pathname:'/schedulingunit/view', state: {id: this.state.schedulingUnit.id}}}>{this.state.schedulingUnit?this.state.schedulingUnit.name:''}</Link> + </> + } </div> <div className="p-field p-grid"> <label htmlFor="tags" className="p-col-2">Template</label> @@ -193,7 +248,7 @@ export class TaskEdit extends Component { </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.saveTask} disabled={!this.state.validEditor} /> + <Button label="Save" className="p-button-primary" icon="pi pi-check" onClick={this.saveTask} disabled={!this.state.validEditor || !this.state.validForm} /> </div> <div className="p-col-1"> <Button label="Cancel" className="p-button-danger" icon="pi pi-times" onClick={this.cancelEdit} /> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/index.js index 7ab687e3398c9015f5b074cdaedbafeeb81f92d0..d7e2c03a6532dbb07064d9ca603f5796bbedd986 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/index.js @@ -1,4 +1,4 @@ import {TaskEdit} from './edit'; import {TaskView} from './view'; -export {TaskEdit, TaskView} ; \ No newline at end of file +export {TaskEdit, TaskView} ; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js index 08355ef7b7d1e720448f2847a8f9ac3dffb83e8d..6fd243298cc249442780efb3fd2ffb93b3f6cc8b 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js @@ -13,14 +13,6 @@ export class TaskView extends Component { super(props); this.state = { }; - this.setEditorOutput = this.setEditorOutput.bind(this); - this.setEditorFunc = this.setEditorFunc.bind(this); - } - - setEditorOutput(outputJson) {} - - setEditorFunc(editorFunc) { - this.setState({editorFunc: editorFunc}); } static getDerivedStateFromProps(nextProps, prevstate){ @@ -39,28 +31,34 @@ export class TaskView extends Component { } componentDidMount() { - const taskId = this.props.taskId?this.props.taskId:1; + const taskId = this.props.location.state?this.props.location.state.id:1; this.getTaskDetails(taskId); } + /** + * To get the task details from the backend using the service + * @param {number} taskId + */ getTaskDetails(taskId) { - TaskService.getTaskDetails("draft", taskId) - .then((task) => { - if (task) { - TaskService.getSchedulingUnit("draft", task.scheduling_unit_draft_id) - .then((schedulingUnit) => { - this.setState({schedulingUnit: schedulingUnit}); - }); - TaskService.getTaskTemplate(task.specifications_template_id) - .then((taskTemplate) => { - if (this.state.editorFunc) { - this.state.editorFunc(); - } - this.setState({task: task, taskTemplate: taskTemplate}); - }); - - } - }); + if (taskId) { + TaskService.getTaskDetails("draft", taskId) + .then((task) => { + if (task) { + TaskService.getSchedulingUnit("draft", task.scheduling_unit_draft_id) + .then((schedulingUnit) => { + this.setState({schedulingUnit: schedulingUnit}); + }); + TaskService.getTaskTemplate(task.specifications_template_id) + .then((taskTemplate) => { + if (this.state.editorFunc) { + this.state.editorFunc(); + } + this.setState({task: task, taskTemplate: taskTemplate}); + }); + + } + }); + } } render() { @@ -71,10 +69,11 @@ export class TaskView extends Component { initValue: this.state.task.specifications_doc, disabled: true, // callback: this.setEditorOutput, - parentFunc: this.setEditorFunc + // parentFunction: this.setEditorFunction }); } + // Child component to render predecessors and successors list const TaskRelationList = ({ list }) => ( <ul className="task-list"> {list.map(item => ( @@ -87,14 +86,13 @@ export class TaskView extends Component { return ( <React.Fragment> - {/* <div style={{marginBottom: "10px"}}> */} <div className="p-grid"> <div className="p-col-3"> <h2>Task - Details </h2> </div> <div className="p-col-1"> <Link to={{ pathname: '/task/edit', state: {taskId: this.state.task?this.state.task.id:''}}} tooltip="Edit Task" > - <i className="pi pi-pencil" style={{marginTop: "10px"}}></i> + <i className="fa fa-edit" style={{marginTop: "10px"}}></i> </Link> </div> </div> @@ -111,15 +109,15 @@ export class TaskView extends Component { <span className="p-col-4">{moment.utc(this.state.task.created_at).format(this.DATE_FORMAT)}</span> <label className="p-col-2">Updated At</label> <span className="p-col-4">{moment.utc(this.state.task.updated_at).format(this.DATE_FORMAT)}</span> - {/* <span className="p-col-4">{moment(this.state.task.updated_at).format()}</span> */} </div> <div className="p-grid"> <label className="p-col-2">Tags</label> <Chips className="p-col-4 chips-readonly" disabled value={this.state.task.tags}></Chips> - {/* <label className="p-col-2">Do Cancel?</label> - <span className="p-col-4">{this.state.task.do_cancel}</span> */} - <label className="p-col-2">Scheduling Unit</label> - <Link className="p-col-4" to={ { pathname:'/scheduling'}}>{this.state.schedulingUnit?this.state.schedulingUnit.name:''}</Link> + {this.state.schedulingUnit && + <> + <label className="p-col-2">Scheduling Unit</label> + <Link className="p-col-4" to={ { pathname:'/schedulingunit/view', state: {id: this.state.schedulingUnit.id}}}>{this.state.schedulingUnit?this.state.schedulingUnit.name:''}</Link> + </>} </div> <div className="p-grid"> <label className="p-col-2">Predecessors</label> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js index ee644b7e50f0d8df7e5fda6d37583a488f00d8eb..af3090f1ef0c4d0331ac3246fd0221a300b8920e 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js @@ -19,7 +19,7 @@ export const RoutedContent = () => { <Route path="/task" exact component={TaskView} /> <Route path="/task/view" exact component={TaskView} /> <Route path="/task/edit" exact component={TaskEdit} /> - <Route path="/viewschedulingunit" exact component={ViewSchedulingUnit} /> + <Route path="/schedulingunit/view" exact component={ViewSchedulingUnit} /> </Switch> ); } \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/ScheduleService.js b/SAS/TMSS/frontend/tmss_webapp/src/services/ScheduleService.js index eeec686f28017096df629e0bdc02172efce444f0..4d06342047389af3579698288dfba8ce0e307342 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/ScheduleService.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/ScheduleService.js @@ -2,7 +2,7 @@ import axios from 'axios' export async function getScheduling_Unit_Draft(){ let res = []; - await axios.get('/api/scheduling_unit_draft/', { + await axios.get('/api/scheduling_unit_draft/?ordering=id', { headers: { "Content-Type": "application/json", "Authorization": "Basic dGVzdDp0ZXN0" @@ -15,10 +15,26 @@ export async function getScheduling_Unit_Draft(){ }); return res; } - -export async function getSchedule_Unit_Draft_By_Id(id){ + +export async function getScheduling_Unit_Draft_By_Id(id){ + let res = []; + await axios.get('/api/scheduling_unit_draft/'+id, { + headers: { + "Content-Type": "application/json", + "Authorization": "Basic dGVzdDp0ZXN0" + } + } + ).then(function(response) { + res= response; + }).catch(function(error) { + console.log('Error on Authentication',error); + }); + return res; +} + +export async function getTasks_Draft_By_scheduling_Unit_Id(id){ let res=[]; - await axios.get('/api/scheduling_unit_draft/'+id+'/task_draft/', { + await axios.get('/api/scheduling_unit_draft/'+id+'/task_draft/?ordering=id', { headers: { "Content-Type": "application/json", "Authorization": "Basic dGVzdDp0ZXN0"