diff --git a/SAS/TMSS/frontend/tmss_webapp/src/App.css b/SAS/TMSS/frontend/tmss_webapp/src/App.css index 1abbf7c90cfc8d3a4b95c5e6d006b90558171bfc..a42d8cdea98d9b9a3e7af9409b1fc521a98d450b 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/App.css +++ b/SAS/TMSS/frontend/tmss_webapp/src/App.css @@ -37,8 +37,19 @@ label { margin-bottom: 10px; } -.main-content span,a{ +h2, .h2 { + font-size: 1.5rem; +} + +h3, .h3 { + font-size: 1.25rem; +} + +a{ margin-bottom: 10px; +} + +.main-content span,a{ font-size: 14px; } @@ -102,8 +113,9 @@ p { margin-bottom: 1rem; } -.fa { +.layout-main .fa { color: #005b9f; + font-size: 20px; } thead { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/App.js b/SAS/TMSS/frontend/tmss_webapp/src/App.js index e9e521a4e4cdfe0ce1a72543801e0ce3c0be0172..4445c3bf0ce5c6d9662006321657cfadf6df59fe 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/App.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/App.js @@ -36,8 +36,11 @@ class App extends Component { {label: 'Dashboard', icon: 'pi pi-fw pi-home', to:'/dashboard'}, {label: 'Scheduling Units', icon: 'pi pi-fw pi-calendar', to:'/schedulingunit'}, {label: 'Tasks', icon: 'pi pi-fw pi-check-square', to:'/task'}, - ]; -} + {label: 'Project', icon: 'fa fa-fw fa-binoculars', to:'/project'} + ]; + + // this.menuComponent = {'Dashboard': Dashboard} + } onWrapperClick(event) { if (!this.menuClick) { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js index 7804c8bf5084c39ba0b0d5fbdf9f8cf8bc659a5f..6d8f5516ebaed9f3790fffaaeb7759868db37748 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js @@ -1,5 +1,4 @@ import React, {useRef } from "react"; -import styled from 'styled-components' import { useSortBy, useTable, useFilters, useGlobalFilter, useAsyncDebounce } from 'react-table' import matchSorter from 'match-sorter' import _ from 'lodash'; @@ -7,35 +6,10 @@ import moment from 'moment'; import { useHistory } from "react-router-dom"; import {OverlayPanel} from 'primereact/overlaypanel'; -const Styles = styled.div` - padding: 1rem; - table { - border-spacing: 0; - - th { - vertical-align: middle!important; - color: #7e8286; - font-size: 14px; - border-bottom: 1px solid lightgray; - border-top: 1px solid lightgray; - padding: .65rem; - } - - td { - padding: .65rem; - border-bottom: 1px solid lightgray; - } - thead>tr>:nth-child(1){ - div { - display: none; - } - } - } -` -let dataheader = [] ; let tbldata =[]; - +let isunittest = false; +let columnclassname =[]; // Define a default UI for filtering function GlobalFilter({ preGlobalFilteredRows, @@ -43,12 +17,10 @@ function GlobalFilter({ setGlobalFilter, }) { - const [value, setValue] = React.useState(globalFilter) - const onChange = useAsyncDebounce(value => {setGlobalFilter(value || undefined)}, 200) - + const [value, setValue] = React.useState(globalFilter) + const onChange = useAsyncDebounce(value => {setGlobalFilter(value || undefined)}, 200) return ( <span> - <input value={value || ""} onChange={e => { @@ -64,7 +36,6 @@ function GlobalFilter({ function DefaultColumnFilter({ column: { filterValue, preFilteredRows, setFilter }, }) { - return ( <input value={filterValue || ''} @@ -74,7 +45,6 @@ function DefaultColumnFilter({ /> ) } - function fuzzyTextFilterFn(rows, id, filterValue) { return matchSorter(rows, filterValue, { keys: [row => row.values[id]] }) @@ -87,18 +57,15 @@ const IndeterminateCheckbox = React.forwardRef( ({ indeterminate, ...rest }, ref) => { const defaultRef = React.useRef() const resolvedRef = ref || defaultRef - React.useEffect(() => { resolvedRef.current.indeterminate = indeterminate }, [resolvedRef, indeterminate]) - return <input type="checkbox" ref={resolvedRef} {...rest} /> } ) // Our table component -function Table({ columns, data }) { - +function Table({ columns, data, defaultheader, optionalheader }) { const filterTypes = React.useMemo( () => ({ // Add a new fuzzyTextFilterFn filter type. @@ -140,16 +107,16 @@ function Table({ columns, data }) { setGlobalFilter, setHiddenColumns, } = useTable( - { - columns, - data, - defaultColumn, - filterTypes, - }, - useFilters, - useGlobalFilter, - useSortBy, - ) + { + columns, + data, + defaultColumn, + filterTypes, + }, + useFilters, + useGlobalFilter, + useSortBy, + ) React.useEffect(() => { setHiddenColumns( @@ -177,8 +144,8 @@ function Table({ columns, data }) { <IndeterminateCheckbox {...getToggleHideAllColumnsProps()} /> Select All </div> {allColumns.map(column => ( - <div key={column.id}> - <input type="checkbox" {...column.getToggleHiddenProps()} />{' '} {_.startCase(column.id)} + <div key={column.id} style={{'display':column.id !== 'actionpath'?'block':'none'}}> + <input type="checkbox" {...column.getToggleHiddenProps()} /> {(defaultheader[column.id])?defaultheader[column.id]:(optionalheader[column.id]?optionalheader[column.id]:column.id)} </div> ))} <br /> @@ -188,31 +155,36 @@ function Table({ columns, data }) { </div> </OverlayPanel> </div> - <div style={{textAlign:'right'}}> - {tbldata.length>1 && - <GlobalFilter - preGlobalFilteredRows={preGlobalFilteredRows} - globalFilter={state.globalFilter} - setGlobalFilter={setGlobalFilter} - /> - } + + <div style={{textAlign:'right'}}> + {tbldata.length>0 && !isunittest && + <GlobalFilter + preGlobalFilteredRows={preGlobalFilteredRows} + globalFilter={state.globalFilter} + setGlobalFilter={setGlobalFilter} + /> + } </div> - </div> - <Styles style={{overflow: 'auto', padding: '0.75em',}}> - <table {...getTableProps()} style={{width:'100%'}}> + + <div style={{overflow: 'auto', padding: '0.75em',}}> + + <table {...getTableProps()} style={{width:'100%'}} data-testid="viewtable" className="viewtable" > <thead> - {headerGroups.map(headerGroup => ( + {headerGroups.map(headerGroup => ( <tr {...headerGroup.getHeaderGroupProps()}> {headerGroup.headers.map(column => ( - <th {...column.getHeaderProps(column.getSortByToggleProps())}> - {column.render('Header')} - <span> - {column.isSorted ? (column.isSortedDesc ? <i className="fa fa-sort-desc" aria-hidden="true"></i> : <i className="fa fa-sort-asc" aria-hidden="true"></i>) : ""} - </span> - {/* Render the columns filter UI */} - <div>{column.canFilter ? column.render('Filter') : null}</div> - </th> + <th {...column.getHeaderProps(column.getSortByToggleProps())} > + {column.Header !== 'actionpath' && column.render('Header')} + {/* {column.Header !== 'Action'? + column.isSorted ? (column.isSortedDesc ? <i className="pi pi-sort-down" aria-hidden="true"></i> : <i className="pi pi-sort-up" aria-hidden="true"></i>) : <i className="pi pi-sort" aria-hidden="true"></i> + : "" + } */} + {/* Render the columns filter UI */} + {column.Header !== 'actionpath' && + <div className={columnclassname[0][column.Header]} > {column.canFilter && column.Header !== 'Action' ? column.render('Filter') : null}</div> + } + </th> ))} </tr> ))} @@ -220,29 +192,24 @@ function Table({ columns, data }) { </thead> <tbody {...getTableBodyProps()}> {firstPageRows.map((row, i) => { + prepareRow(row) return ( <tr {...row.getRowProps()}> - {row.cells.map(cell => { - //>>>>>> Update this code when merging with recent code - if (cell.column.Header === 'Action') { - return <td {...cell.getCellProps()}>{cell.render('Cell')}</td> - } else if (typeof(cell.value) === 'string' || typeof(cell.value) === 'number') { - return <td {...cell.getCellProps()}>{cell.value}</td> - } else { - return <td {...cell.getCellProps()}></td> - } - //<<<<<< + {row.cells.map(cell => { + if(cell.column.id !== 'actionpath') + return <td {...cell.getCellProps()} >{cell.render('Cell')}</td> })} </tr> ) })} </tbody> </table> - </Styles> + </div> </> ) } + // Define a custom filter filter function! function filterGreaterThan(rows, id, filterValue) { @@ -259,90 +226,103 @@ function filterGreaterThan(rows, id, filterValue) { filterGreaterThan.autoRemove = val => typeof val !== 'number' function ViewTable(props) { - const history = useHistory(); // Data to show in table tbldata = props.data; + isunittest = props.unittest; + columnclassname = props.columnclassname; + // Default Header to show in table and other columns header will not show until user action on UI let defaultheader = props.defaultcolumns; + let optionalheader = props.optionalcolumns; + let columns = []; let defaultdataheader = Object.keys(defaultheader[0]); + let optionaldataheader = Object.keys(optionalheader[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>, + id:'Action', + accessor: props.keyaccessor, + Cell: props => <button className='p-link' onClick={navigateTo(props)} ><i className="fa fa-edit" style={{cursor: 'pointer'}}></i></button>, disableFilters: true, disableSortBy: true, - isVisible: defaultdataheader.includes('id'), + isVisible: defaultdataheader.includes(props.keyaccessor), }) } + const navigateTo = (props) => () => { + if(props.cell.row.values['actionpath']){ + return history.push({ + pathname: props.cell.row.values['actionpath'], + state: { + "id": props.value, + } + }) + } + // Object.entries(props.paths[0]).map(([key,value]) =>{}) + + + } + + //Default Columns defaultdataheader.forEach(header =>{ columns.push({ Header: defaultheader[0][header], + id: defaultheader[0][header], accessor: header, filter: 'fuzzyText', isVisible: true, - Cell: props => <div> {dateformat(props.value)} </div>, - minWidth: 20, + Cell: props => <div> {updatedCellvalue(header, props.value)} </div>, }) }) - dataheader = Object.keys(tbldata[0]); - dataheader.forEach(header => { - if(!defaultdataheader.includes(header)){ - var text = _.startCase(header); - columns.push({ - Header: text, - accessor: header, - filter: 'fuzzyText', - isVisible: defaultheader.includes(header), - Cell: props => <div> {dateformat(props.value)} </div>, - minWidth: 20, - }) - } + //Optional Columns + optionaldataheader.forEach(header => { + columns.push({ + Header: optionalheader[0][header], + id: header, + accessor: header, + filter: 'fuzzyText', + isVisible: false, + Cell: props => <div> {updatedCellvalue(header, props.value)} </div>, + }) }); - - function dateformat(date){ + + function updatedCellvalue(key, value){ try{ - if(date && date.length===26){ - var result = moment(date).format("YYYY-MM-DD HH:mm:SS") - if(result === 'Invalid date'){ - return date; - }else{ - return result; - } - } + if(key === 'blueprint_draft' && _.includes(value,'/task_draft/')){ + // 'task_draft/' -> len = 12 + var taskid = _.replace(value.substring((value.indexOf('/task_draft/')+12), value.length),'/',''); + return <a href={'/task/view/draft/'+taskid}>{' '+taskid+' '}</a> + }else if(key === 'blueprint_draft'){ + var retval= []; + value.forEach((link, index) =>{ + // 'task_blueprint/' -> len = 16 + if(_.includes(link,'/task_blueprint/')){ + var bpid = _.replace(link.substring((link.indexOf('/task_blueprint/')+16), link.length),'/',''); + retval.push( <a href={'/task/view/blueprint/'+bpid} key={bpid+index} >{' '+bpid+' '}</a> ) + } + }) + return retval; + }else if(typeof value == "string"){ + const dateval = moment(value, moment.ISO_8601).format("YYYY-MMM-DD HH:mm:SS"); + if(dateval !== 'Invalid date'){ + return dateval; + } + } }catch(err){ - console.err('Error',err) + console.error('Error',err) } - return date; + return value; } - - // 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} className="-striped -highlight" /> - + <div> + <Table columns={columns} data={tbldata} defaultheader={defaultheader[0]} optionalheader={optionalheader[0]} /> </div> ) } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_layout.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_layout.scss index 93efe5f2d1b91890845517dad3784b7f831843b6..79f22ea107106c4e26e10b2cc375414ea77b293d 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_layout.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_layout.scss @@ -11,3 +11,4 @@ @import "./_utils"; @import "./_dashboard"; @import "./_breadcrumb"; +@import "./_viewtable"; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_viewtable.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_viewtable.scss new file mode 100644 index 0000000000000000000000000000000000000000..9a8abc949f663e016266980de3c8df80863e0af2 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_viewtable.scss @@ -0,0 +1,45 @@ + .viewtable{ + overflow: auto !important; + padding: 0.75em; + } + +.viewtable th { + color: #7e8286; + font-size: 14px; + border-bottom: 1px solid lightgray; + border-top: 1px solid lightgray; + padding: .65rem; + vertical-align:bottom; + } + +.viewtable th>div { + vertical-align: text-bottom; +} + +.viewtable td { + font-size: 14px; + padding: .65rem; + border-bottom: 1px solid lightgray; + overflow-wrap: anywhere; +} + +.filter-input input{ + max-width: 150px; +} + +.filter-input-50 input{ + width: 50px; +} + +.filter-input-70 input{ + width: 70px; +} + +.filter-input-100 input{ + width: 100px; +} + +.filter-input-150 input{ +width: 150px; +} + \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/create.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/create.js index 01160674db3155a1cac53b41b660d9b0e5392075..2ef07f64457071add2a9d68ae0383350268bfbc9 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/create.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/create.js @@ -333,11 +333,11 @@ export class ProjectCreate extends Component { <div className="p-grid"> <Growl ref={(el) => this.growl = el} /> - <div className="p-col-10 p-lg-3 p-md-4"> + <div className="p-col-10 p-lg-10 p-md-10"> <h2>Project - Add</h2> </div> - <div className="p-col-2 p-lg-3 p-md-4"> - <Link to={{ pathname: '/project'}} tooltip="Close Edit" > + <div className="p-col-2 p-lg-2 p-md-2"> + <Link to={{ pathname: '/project'}} tite="Close Edit" style={{float: "right"}}> <i className="fa fa-window-close" style={{marginTop: "10px"}}></i> </Link> </div> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/edit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/edit.js index 2429356d4373aca36ba2ed51d1e603500d19431e..cfb08e5d9ff078c263694a391dffbf76b91db01c 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/edit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/edit.js @@ -356,11 +356,11 @@ export class ProjectEdit extends Component { <div className="p-grid"> <Growl ref={(el) => this.growl = el} /> - <div className="p-col-10 p-lg-3 p-md-4"> + <div className="p-col-10 p-lg-10 p-md-10"> <h2>Project - Edit</h2> </div> - <div className="p-col-2 p-lg-3 p-md-4"> - <Link to={{ pathname: `/project/view/${this.state.project.name}`}} tooltip="Close Edit" > + <div className="p-col-2 p-lg-2 p-md-2"> + <Link to={{ pathname: `/project/view/${this.state.project.name}`}} title="Close Edit" style={{float: "right"}}> <i className="fa fa-window-close" style={{marginTop: "10px"}}></i> </Link> </div> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/index.js index 31844fd88b2aa4b5373b851f61cf9007399d319a..7572b27b8d71777ba3a584f56a054438cdd4b13e 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/index.js @@ -1,5 +1,6 @@ import {ProjectCreate} from './create'; import {ProjectView} from './view'; import {ProjectEdit} from './edit'; +import {ProjectList} from './list'; -export {ProjectCreate, ProjectView, ProjectEdit} ; +export {ProjectList, ProjectCreate, ProjectView, ProjectEdit} ; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/list.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/list.js new file mode 100644 index 0000000000000000000000000000000000000000..d9de45db21a1e3c59793247a84aa825148b1021b --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/list.js @@ -0,0 +1,126 @@ +import React, {Component} from 'react'; +import ProjectService from '../../services/project.services'; +import ViewTable from '../../components/ViewTable'; +import { Link } from 'react-router-dom/cjs/react-router-dom.min'; +import AppLoader from '../../layout/components/AppLoader'; + +export class ProjectList extends Component{ + constructor(props){ + super(props) + this.state = { + projectlist: [], + paths: [{ + "View": "/project/view", + }], + defaultcolumns: [ { + "name":"Name / Project Code", + "status":"Status" , + "project_category_value":"Category of Project", + "description":"Description" + }], + optionalcolumns: [{ + "priority_rank":"Project Priority", + "trigger_priority":"Trigger Priority", + "period_category_value":"Category of Period", + "cycles_ids":"Cycles", + "LOFAR Observing Time":"Observing time (Hrs)", + "LOFAR Observing Time prio A":"Observing time prio A (Hrs)", + "LOFAR Observing Time prio B":"Observing time prio B (Hrs)", + "CEP Processing Time":"Processing time (Hrs)", + "LTA Storage":"LTA storage (TB)", + "Number of triggers":"Triggers allowed", + "actionpath":"actionpath", + }], + columnclassname: [{ + "Observing time (Hrs)":"filter-input-50", + "Observing time prio A (Hrs)":"filter-input-50", + "Observing time prio B (Hrs)":"filter-input-50", + "Processing time (Hrs)":"filter-input-50", + "LTA storage (TB)":"filter-input-50", + "Status":"filter-input-50", + "Triggers allowed":"filter-input-50", + "Project Priority":"filter-input-50", + "Trigger Priority":"filter-input-50", + "Category of Period":"filter-input-50", + "Cycles":"filter-input-100", + }], + isprocessed: false, + isLoading: true + } + } + + componentDidMount(){ + // for Unit test, Table data + this.unittestDataProvider(); + ProjectService.getProjectList() + .then(async (projects) => { + await ProjectService.getUpdatedProjectQuota(projects) + .then( async projlist => { + this.setState({ + projectlist: projlist, + isprocessed: true, + isLoading: false + }) + }) + }); + } + + render(){ + return( + <> + <div className="p-grid"> + <div className="p-col-10 p-lg-10 p-md-10"> + <h2>Project - List </h2> + </div> + <div className="p-col-2 p-lg-2 p-md-2"> + <Link to={{ pathname: '/project/create'}} title="Add New Project" style={{float: "right"}}> + <i className="fa fa-plus-square" style={{marginTop: "10px"}}></i> + </Link> + </div> + </div> + {this.state.isLoading? <AppLoader /> : this.state.isprocessed && + <ViewTable + data={this.state.projectlist} + defaultcolumns={this.state.defaultcolumns} + optionalcolumns={this.state.optionalcolumns} + columnclassname={this.state.columnclassname} + showaction="true" + paths={this.state.paths} + keyaccessor="name" + unittest={this.state.unittest} + /> + } + </> + ) + } + + // Set table data for Unit test + unittestDataProvider(){ + if(this.props.testdata){ + this.setState({ + projectlist: [{can_trigger: true, + created_at: "2020-07-27T01:29:57.348499", + cycles: ["http://localhost:3000/api/cycle/Cycle%204/"], + cycles_ids: ["Cycle 4"], + description: "string", + expert: true, + filler: true, + name: "Lofar-TMSS-Commissioning", + observing_time: "155852.10", + priority_rank: 10, + private_data: true, + project_quota: ["http://localhost:3000/api/project_quota/6/", "http://localhost:3000/api/project_quota/7/"], + project_quota_ids: [6, 7], + tags: ["Lofar TMSS Project"], + trigger_priority: 20, + triggers_allowed: "56", + updated_at: "2020-07-27T01:29:57.348522", + url: "http://localhost:3000/api/project/Lofar-TMSS-Commissioning/" + }], + isprocessed: true, + unittest: true, + }) + } + } +} + \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/list.test.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/list.test.js new file mode 100644 index 0000000000000000000000000000000000000000..323c91575f39c9aa00a5a4bd6ab43b7e54c086b1 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/list.test.js @@ -0,0 +1,39 @@ +import React from 'react'; +import ReactDOM, {unmountComponentAtNode} from 'react-dom'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { act } from 'react-dom/test-utils'; +import { render} from '@testing-library/react'; +import { ProjectList} from './index'; + +let container = null; +beforeEach(() =>{ + container = document.createElement("div"); + document.body.appendChild(container); +}); + +afterEach(() =>{ + unmountComponentAtNode(container); + container.remove(); + container = null; +}) + +it("renders without crashing", () =>{ + act(() =>{ + ReactDOM.render(<Router><ProjectList /> </Router>, container); + }) +}) + +// Do check the label appear or not +it('renders Project - List Page in View Table', () => { + const content = render(<ProjectList />); + const element = content.queryByText("Project - List"); + expect(element).toBeInTheDocument() ; + }); + +// do check does the data loaded into DB or not +it('renders Project - List Data Load in View Table', () => { + const content = render(<Router><ProjectList testdata= {true} /> </Router>, container); + const element = content.queryByTestId('viewtable'); + expect(element).toBeInTheDocument(); +}); + \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js index 313ac80590184c627b5ea6741ff6e97e07aefe14..cfcfe65aa8a907f3b12c4e39e49c3bd04e066f67 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js @@ -20,17 +20,23 @@ export class ProjectView extends Component { super(props); this.state = { isLoading: true, - redirect: this.props.match.params.id?"":'/project/list' // If no project id is passed, redirect to Project list page }; + console.log(this.props); if (this.props.match.params.id) { this.state.projectId = this.props.match.params.id; + } else if (this.props.location.state && this.props.location.state.id) { + this.state.projectId = this.props.location.state.id; } + console.log(this.state.projectId); + this.state.redirect = this.state.projectId?"":'/project' // If no project id is passed, redirect to Project list page this.resourceUnitMap = UnitConverter.resourceUnitMap; // Resource unit conversion factor and constraints } componentDidMount() { - const projectId = this.props.location.state?this.props.location.state.id:this.state.projectId; + const projectId = this.state.projectId; + console.log(projectId); if (projectId) { + console.log(projectId); this.getProjectDetails(projectId); } else { this.setState({redirect: "/not-found"}); @@ -42,7 +48,7 @@ export class ProjectView extends Component { * */ async getProjectDetails() { - let project = await ProjectService.getProjectDetails(this.props.match.params.id); + let project = await ProjectService.getProjectDetails(this.state.projectId); let projectQuota = []; let resources = []; @@ -74,12 +80,16 @@ export class ProjectView extends Component { return ( <React.Fragment> <div className="p-grid"> - <div className="p-col-10 p-lg-3 p-md-4"> + <div className="p-col-10 p-lg-10 p-md-10"> <h2>Project - Details </h2> </div> { this.state.project && - <div className="p-col-2 p-lg-3 p-md-4"> - <Link to={{ pathname: `/project/edit/${this.state.project.name}`, state: {id: this.state.project?this.state.project.name:''}}} tooltip="Edit Project" > + <div className="p-col-2 p-lg-2 p-md-2"> + <Link to={{ pathname: `/project`}} title="Close View" style={{float: "right"}}> + <i className="fa fa-times" style={{marginTop: "10px", marginLeft: "5px"}}></i> + </Link> + <Link to={{ pathname: `/project/edit/${this.state.project.name}`, state: {id: this.state.project?this.state.project.name:''}}} title="Edit Project" + style={{float: "right"}}> <i className="fa fa-edit" style={{marginTop: "10px"}}></i> </Link> </div> @@ -128,8 +138,10 @@ export class ProjectView extends Component { </div> {this.state.projectQuota.length===0 && <div className="p-field p-grid"> - <div className="col-lg-3 col-md-3 col-sm-12"> - <span>Reosurces not yet allocated</span> + <div className="col-lg-12 col-md-12 col-sm-12"> + <span>Reosurces not yet allocated. + <Link to={{ pathname: `/project/edit/${this.state.project.name}`, state: {id: this.state.project?this.state.project.name:''}}} title="Edit Project" > Click</Link> to add. + </span> </div> </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 c3c5fa84c9823756aa13817cc73e9835c539e0e8..d7bf9118ce0ada33008f03730b02407d38518b88 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js @@ -1,8 +1,11 @@ import React, { Component } from 'react' import 'primeflex/primeflex.css'; -import ViewTable from './../../components/ViewTable'; -import {getScheduling_Unit_Draft} from '../../services/ScheduleService'; + import AppLoader from "./../../layout/components/AppLoader"; +import ViewTable from './../../components/ViewTable'; + +import ScheduleService from '../../services/schedule.service'; + class SchedulingUnitList extends Component{ @@ -13,13 +16,33 @@ class SchedulingUnitList extends Component{ paths: [{ "View": "/schedulingunit/view", }], - isLoading: false, + isLoading: true, + defaultcolumns: [ { + "name":"Name", + "description":"Description", + "created_at":"Created Date", + "updated_at":"Updated Date", + "requirements_template_id": "Template", + "start_time":"Start Time", + "stop_time":"End time", + "duration":"Duration" + }], + optionalcolumns: [{ + "actionpath":"actionpath", + }], + columnclassname: [{ + "Template":"filter-input-50", + "Duration":"filter-input-50", + }] } } componentDidMount(){ - this.setState({ isLoading: true }); - getScheduling_Unit_Draft().then(scheduleunit =>{ + ScheduleService.getSchedulingUnitDraft().then(scheduleunit =>{ + var scheduleunits = scheduleunit.data.results; + for( const scheduleunit of scheduleunits){ + scheduleunit['actionpath']='/schedulingunit/view' + } this.setState({ scheduleunit : scheduleunit.data ,isLoading: false }); @@ -27,19 +50,9 @@ class SchedulingUnitList extends Component{ } render(){ - const {isLoading } = this.state; - if (isLoading) { - return <AppLoader/> - } - console.log(this.state.scheduleunit); - if(this.state.scheduleunit.results){ - this.state.scheduleunit.results.forEach(item =>{ - delete item['requirements_doc'] - }) + if (this.state.isLoading) { + return <AppLoader/> } - // 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( <> @@ -48,16 +61,23 @@ class SchedulingUnitList extends Component{ /* * 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 + defaultcolumns - These columns will be populate by default in table with header mentioned + optionalcolumns - These columns will not appear in the table by default, but user can toggle the columns using toggle menu showaction - {true/false} -> to show the action column + keyaccessor - This is id column for Action item paths - specify the path for navigation - Table will set "id" value for each row in action button + */} {this.state.scheduleunit.results && <ViewTable data={this.state.scheduleunit.results} - defaultcolumns={defaultcolumns} + defaultcolumns={this.state.defaultcolumns} + optionalcolumns={this.state.optionalcolumns} + columnclassname={this.state.columnclassname} showaction="true" + keyaccessor="id" paths={this.state.paths} + unittest={this.state.unittest} /> } </> 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 509a1d80e690e4ee14b7c34483a78205686958c2..4b3c24179fb7025275c45512fb753e32fb5822a5 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js @@ -2,34 +2,67 @@ import React, { Component } from 'react' 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'; + import AppLoader from "./../../layout/components/AppLoader"; +import ViewTable from './../../components/ViewTable'; +import ScheduleService from '../../services/schedule.service'; +import moment from 'moment'; class ViewSchedulingUnit extends Component{ - constructor(props){ super(props) this.state = { scheduleunit: null, - schedule_unit_task: [], + schedule_unit_task: [], + isLoading: true, paths: [{ "View": "/task", }], - } + + defaultcolumns: [ { + "tasktype":"Task Type", + "id":"ID", + "name":"Name", + "description":"Description", + "created_at":"Created at", + "updated_at":"Updated at", + "do_cancel":"Cancelled", + "start_time":"Start Time", + "stop_time":"End Time", + "duration":"Duration", + }], + optionalcolumns: [{ + "relative_start_time":"Relative Start Time", + "relative_stop_time":"Relative End Time", + "tags":"Tags", + "blueprint_draft":"BluePrint / Task Draft link", + "url":"URL", + "actionpath":"actionpath", + }], + + columnclassname: [{ + "Task Type":"filter-input-100", + "ID":"filter-input-50", + "Cancelled":"filter-input-50", + "Duration":"filter-input-50", + "Template ID":"filter-input-50", + "BluePrint / Task Draft link":"filter-input-100", + }] + } } - componentDidMount(){ - this.setState({ isLoading: true }); - let schedule_id = this.props.location.state && 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({ + componentDidMount(){ + let schedule_id = this.props.location.state.id + if (schedule_id) { + ScheduleService.getSchedulingUnitDraftById(schedule_id) + .then(scheduleunit =>{ + ScheduleService.getScheduleTasksBySchedulingUnitId(scheduleunit.data.id) + .then(tasks =>{ + this.setState({ scheduleunit : scheduleunit.data, - schedule_unit_task : tasks.data.results, - isLoading:false + schedule_unit_task : tasks, + isLoading: false }); }); }) @@ -37,29 +70,24 @@ class ViewSchedulingUnit extends Component{ } render(){ - const {isLoading } = this.state; - 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"}] - let defaultcolumns = [ {"id":"Task Identifier","name":"Task Name","description":"Task Description","created_at":"Created Date","updated_at":"Updated Date"}] return( <> <div className="p-grid"> - <div className="p-col-5"> + <div className="p-col-10"> <h2>Scheduling Unit - Details </h2> </div> - <div className="p-col-1"> - <Link to={{ pathname: '/Scheduling/edit', state: {id: this.state.scheduleunit?this.state.scheduleunit.id:''}}} tooltip="Edit" > + <div className="p-col-2"> + <Link to={{ pathname: '/schedulingunit'}} title="Close" + style={{float:'right'}}> + <i className="fa fa-times" style={{marginTop: "10px", marginLeft: '5px'}}></i> + </Link> + <Link to={{ pathname: '/schedulingunit/edit', state: {id: this.state.scheduleunit?this.state.scheduleunit.id:''}}} title="Edit" + style={{float:'right'}}> <i className="fa fa-edit" style={{marginTop: "10px"}}></i> </Link> </div> </div> - { isLoading ? <AppLoader/> :this.state.scheduleunit && + { this.state.isLoading ? <AppLoader/> :this.state.scheduleunit && <> <div className="p-grid"> <label className="p-col-2">Name</label> @@ -69,11 +97,25 @@ class ViewSchedulingUnit extends Component{ </div> <div className="p-grid"> <label className="p-col-2">Created At</label> - <span className="p-col-4">{this.state.scheduleunit.created_at}</span> + <span className="p-col-4">{moment(this.state.scheduleunit.created_at).format("YYYY-MMM-DD HH:mm:SS")}</span> <label className="p-col-2">Updated At</label> - <span className="p-col-4">{this.state.scheduleunit.updated_at}</span> + <span className="p-col-4">{moment(this.state.scheduleunit.updated_at).format("YYYY-MMM-DD HH:mm:SS")}</span> + </div> + <div className="p-grid"> + <label className="p-col-2">Start Time</label> + <span className="p-col-4">{this.state.scheduleunit.start_time && moment(this.state.scheduleunit.start_time).format("YYYY-MMM-DD HH:mm:SS")}</span> + <label className="p-col-2">End Time</label> + <span className="p-col-4">{this.state.scheduleunit.stop_time && moment(this.state.scheduleunit.stop_time).format("YYYY-MMM-DD HH:mm:SS")}</span> + </div> + <div className="p-grid"> + <label className="p-col-2">Template ID</label> + <span className="p-col-4">{this.state.scheduleunit.requirements_template_id}</span> + <label className="p-col-2">Scheduling set</label> + <span className="p-col-4">{this.state.scheduleunit.scheduling_set_id}</span> </div> <div className="p-grid"> + <label className="p-col-2">Duration</label> + <span className="p-col-4">{this.state.scheduleunit.duration}</span> <label className="p-col-2">Tags</label> <Chips className="p-col-4 chips-readonly" disabled value={this.state.scheduleunit.tags}></Chips> <span className="p-col-4">{this.state.scheduleunit.tags}</span> @@ -81,25 +123,31 @@ class ViewSchedulingUnit extends Component{ </> } - - <div style={{marginTop: '20px'}}> + <div> <h3>Tasks Details</h3> </div> {/* * 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 + data - Pass API data + defaultcolumns - These columns will be populate by default in table with header mentioned + optionalcolumns - These columns will not appear in the table by default, but user can toggle the columns using toggle menu showaction - {true/false} -> to show the action column + keyaccessor - This is id column for Action item paths - specify the path for navigation - Table will set "id" value for each row in action button + */} - {isLoading ? <AppLoader/> :this.state.schedule_unit_task.length>0 && - <ViewTable - data ={this.state.schedule_unit_task} - defaultcolumns={defaultcolumns} + {this.state.isLoading ? <AppLoader/> :this.state.schedule_unit_task.length>0 && + <ViewTable + data={this.state.schedule_unit_task} + defaultcolumns={this.state.defaultcolumns} + optionalcolumns={this.state.optionalcolumns} + columnclassname={this.state.columnclassname} showaction="true" + keyaccessor="id" paths={this.state.paths} - /> - } + unittest={this.state.unittest} + /> + } </> ) } 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 1207110d822ab3febed7fb487a7311035db8dd93..c3caf1543f3ca4fa398ef7113c897e7b419635aa 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/edit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/edit.js @@ -32,7 +32,7 @@ export class TaskEdit extends Component { validEditor: false, validForm: false, errors: {}, - isLoading: false + isLoading: true }; this.formRules = { name: {required: true, message: "Name can not be empty"}, @@ -155,7 +155,7 @@ export class TaskEdit extends Component { this.templateOutput[task.specifications_template_id] = task.specifications_doc; TaskService.getTaskTemplate(task.specifications_template_id) .then((taskTemplate) => { - this.setState({task: task, taskSchema: taskTemplate.schema}); + this.setState({task: task, taskSchema: taskTemplate.schema, isLoading: false}); }); } else { this.setState({redirect: "/not-found"}); @@ -188,11 +188,12 @@ export class TaskEdit extends Component { return ( <React.Fragment> <div className="p-grid"> - <div className="p-col-10 p-lg-3 p-md-4"> + <div className="p-col-10 p-lg-10 p-md-10"> <h2>Task - Edit</h2> </div> - <div className="p-col-2 p-lg-3 p-md-4"> - <Link to={{ pathname: '/task', state: {id: this.state.task?this.state.task.id:''}}} tooltip="Close Edit" > + <div className="p-col-2 p-lg-2 p-md-2"> + <Link to={{ pathname: `/task/view/draft/${this.state.task?this.state.task.id:''}`}} title="Close Edit" + style={{float: "right"}} > <i className="fa fa-window-close" style={{marginTop: "10px"}}></i> </Link> </div> 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 625cd9668fce415835d4316de3986918764e45c0..461b4f945a50b9199b27c8eed942b665301f4ead 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js @@ -1,17 +1,19 @@ import React, {Component} from 'react'; import {Link, Redirect} from 'react-router-dom' import moment from 'moment'; + import Jeditor from '../../components/JSONEditor/JEditor'; + import TaskService from '../../services/task.services'; -import AppLoader from "./../../layout/components/AppLoader"; import { Chips } from 'primereact/chips'; +import AppLoader from '../../layout/components/AppLoader'; export class TaskView extends Component { DATE_FORMAT = 'YYYY-MMM-DD HH:mm:ss'; constructor(props) { super(props); this.state = { - isLoading: false + isLoading: true }; this.setEditorFunction = this.setEditorFunction.bind(this); if (this.props.match.params.id) { @@ -20,35 +22,41 @@ export class TaskView extends Component { if (this.props.match.params.type) { this.state.taskType = this.props.match.params.type; } + } - static getDerivedStateFromProps(nextProps, prevstate){ - - if (prevstate.task && nextProps.location.state && - (nextProps.location.state.taskId === prevstate.task.id || - nextProps.location.state.taskType === prevstate.taskType)) { - return {taskId: prevstate.task.id, taskType: prevstate.taskType} - } - return null; - } + // static getDerivedStateFromProps(nextProps, prevstate){ + // console.log("DERIVED STATE FROM PROPS"); + // console.log(nextProps); + // console.log(prevstate); + // if (prevstate.task && nextProps.match.params && + // (nextProps.match.params.id === prevstate.task.id || + // nextProps.match.params.type === prevstate.taskType)) { + // return {taskId: prevstate.task.id, taskType: prevstate.taskType} + // } + // console.log("RETURNS NULL"); + // return null; + // } componentDidUpdate(prevProps, prevState) { - if (this.state.task && this.props.location.state && - (this.state.task.id !== this.props.location.state.taskId || - this.state.taskType !== this.props.location.state.taskType)) { - this.getTaskDetails(this.props.location.state.taskId, this.props.location.state.taskType); - } + if (this.state.task && this.props.match.params && + (this.state.taskId !== this.props.match.params.id || + this.state.taskType !== this.props.match.params.type)) { + this.getTaskDetails(this.props.match.params.id, this.props.match.params.type); + } } componentDidMount() { - const taskId = this.props.location.state?this.props.location.state.id:this.state.taskId; - let taskType = this.props.location.state?this.props.location.state.type:this.state.taskType; - taskType = taskType?taskType:'draft'; + // const taskId = this.props.location.state?this.props.location.state.id:this.state.taskId; + // let taskType = this.props.location.state?this.props.location.state.type:this.state.taskType; + // taskType = taskType?taskType:'draft'; + let {taskId, taskType} = this.state; + taskId = taskId?taskId:this.props.location.state.id; + taskType = taskType?taskType:this.props.location.state.type; + if (taskId && taskType) { - this.setState({ isLoading: true }); this.getTaskDetails(taskId, taskType); } else { - this.setState({redirect: "/not-found"}); } } @@ -58,7 +66,6 @@ export class TaskView extends Component { * @param {Function} editorFunction */ setEditorFunction(editorFunction) { - this.setState({editorFunction: editorFunction}); } @@ -67,23 +74,24 @@ export class TaskView extends Component { * @param {number} taskId */ getTaskDetails(taskId, taskType) { - if (taskId) { + if (taskId) { taskType = taskType?taskType:'draft'; - TaskService.getTaskDetails(taskType, taskId) - .then((task) => { - if (task) { - TaskService.getSchedulingUnit(taskType, (taskType==='draft'?task.scheduling_unit_draft_id:task.scheduling_unit_blueprint_id)) - .then((schedulingUnit) => { - this.setState({schedulingUnit: schedulingUnit,isLoading:false}); - }); - TaskService.getTaskTemplate(task.specifications_template_id) - .then((taskTemplate) => { - if (this.state.editorFunction) { - this.state.editorFunction(); + TaskService.getTaskDetails(taskType, taskId) + .then((task) => { + if (task) { + TaskService.getSchedulingUnit(taskType, (taskType==='draft'?task.scheduling_unit_draft_id:task.scheduling_unit_blueprint_id)) + .then((schedulingUnit) => { + this.setState({schedulingUnit: schedulingUnit}); + }); + TaskService.getTaskTemplate(task.specifications_template_id) + .then((taskTemplate) => { + if (this.state.editorFunction) { + this.state.editorFunction(); } - this.setState({task: task, taskTemplate: taskTemplate, taskType: taskType,isLoading:false}); + this.setState({task: task, taskTemplate: taskTemplate, isLoading: false, taskId: taskId, taskType: taskType}); }); - } else { + + } else { this.setState({redirect: "/not-found"}); } }); @@ -91,49 +99,56 @@ export class TaskView extends Component { } render() { - const { isLoading } = this.state; if (this.state.redirect) { return <Redirect to={ {pathname: this.state.redirect} }></Redirect> } let jeditor = null if (this.state.taskTemplate) { - jeditor = React.createElement(Jeditor, {title: "Specification", - schema: this.state.taskTemplate.schema, + jeditor = React.createElement(Jeditor, {title: "Specification", + schema: this.state.taskTemplate.schema, initValue: this.state.task.specifications_doc, disabled: true, // callback: this.setEditorOutput, - parentFunction: this.setEditorFunction, - }); + parentFunction: this.setEditorFunction + }); } - - // Child component to render predecessors and successors list + + // Child component to render predecessors and successors list const TaskRelationList = ({ list }) => ( - <ul className="task-list"> + <ul className="task-list"> {list && list.map(item => ( <li key={item.id}> - <Link to={ { pathname:'/task', state: {taskId: item.id, taskType: item.draft?'blueprint':'draft'}}}>{item.name}</Link> + {/* <Link to={ { pathname:'/task/view', state: {id: item.id, type: item.draft?'blueprint':'draft'}}}>{item.name}</Link> */} + <Link to={ { pathname:`/task/view/${item.draft?'blueprint':'draft'}/${item.id}`}}>{item.name}</Link> </li> - ))} + ))} </ul> - ); - return ( - <React.Fragment> + ); + return ( + <React.Fragment> <div className="p-grid"> - <div className="p-col-10 p-lg-3 p-md-4"> + <div className="p-col-10 p-lg-10 p-md-10"> <h2>Task - Details </h2> </div> - <div className="p-col-2 p-lg-3 p-md-4"> + <div className="p-col-2 p-lg-2 p-md-2"> {this.state.taskType === 'draft' && - <Link to={{ pathname: '/task/edit', state: {taskId: this.state.task?this.state.task.id:''}}} tooltip="Edit Task" > + <div> + <Link to={{ pathname: '/task'}} tooltip="Edit Task" + style={{float: 'right'}}> + <i className="fa fa-times" style={{marginLeft:"5px", marginTop: "10px"}}></i> + </Link> + <Link to={{ pathname: '/task/edit', state: {taskId: this.state.task?this.state.task.id:''}}} tooltip="Edit Task" + style={{float: 'right'}}> <i className="fa fa-edit" style={{marginTop: "10px"}}></i> </Link> + </div> } {this.state.taskType === 'blueprint' && - <i className="fa fa-lock" style={{marginTop: "10px"}}></i> + <i className="fa fa-lock" style={{float:"right", marginTop: "10px"}}></i> } </div> </div> - { isLoading ? <AppLoader/> :this.state.task && + { this.state.isLoading? <AppLoader /> : this.state.task && <React.Fragment> <div className="main-content"> <div className="p-grid"> @@ -188,7 +203,8 @@ export class TaskView extends Component { <TaskRelationList list={this.state.task.blueprints} /> } {this.state.taskType === 'blueprint' && - <Link className="col-lg-4 col-md-4 col-sm-12" to={ { pathname:'/task', state: {taskId: this.state.task.draft_id, taskType: 'draft'}}}>{this.state.task.draftObject.name}</Link> + // <Link className="col-lg-4 col-md-4 col-sm-12" to={ { pathname:'/task/view', state: {id: this.state.task.draft_id, type: 'draft'}}}>{this.state.task.draftObject.name}</Link> + <Link className="col-lg-4 col-md-4 col-sm-12" to={ { pathname:`/task/view/draft/${this.state.task.draft_id}`}}>{this.state.task.draftObject.name}</Link> } </div> </div> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js index c73503598498a90804174c595d8e19da81ea5abe..8d4c63793257e35d53829a1b72ea630c0cfd9b0a 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js @@ -6,7 +6,7 @@ import { } from 'react-router-dom'; import {NotFound} from '../layout/components/NotFound'; -import {ProjectCreate, ProjectView, ProjectEdit} from './Project'; +import {ProjectList, ProjectCreate, ProjectView, ProjectEdit} from './Project'; import {Dashboard} from './Dashboard'; import {Scheduling} from './Scheduling'; import {TaskEdit, TaskView} from './Task'; @@ -46,12 +46,16 @@ export const routes = [ name: 'Scheduling View' },{ path: "/project", - component: NotFound, + component: ProjectList, name: 'Project List' },{ path: "/project/create", component: ProjectCreate, name: 'Project Add' + },{ + path: "/project/view", + component: ProjectView, + name: 'Project View' },{ path: "/project/view/:id", component: ProjectView, diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/project.services.js b/SAS/TMSS/frontend/tmss_webapp/src/services/project.services.js new file mode 100644 index 0000000000000000000000000000000000000000..88988e0527b2521da7a81c105035b2bf74044dff --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/project.services.js @@ -0,0 +1,92 @@ +import UnitConverter from './../utils/unit.converter' +const axios = require('axios'); + +//axios.defaults.baseURL = 'http://192.168.99.100:8008/api'; +axios.defaults.headers.common['Authorization'] = 'Basic dGVzdDp0ZXN0'; + +const ProjectService = { + getProjectList: async function() { + try { + const response = await axios.get('/api/project/'); + return response.data.results; + } catch (error) { + console.error('[project.services.getProjectList]',error); + } + }, + getResourceUnitType: async function(resource_type_id, resourceTypes){ + let res_unit_type = ''; + try{ + await resourceTypes.forEach(resourcetype => { + if(resourcetype.name === resource_type_id){ + res_unit_type = resourcetype.name; + return res_unit_type; + } + }); + } catch (error) { + console.error('[project.services.getResourceUnitType]',error); + } + return res_unit_type; + }, + + getUpdatedProjectQuota: async function(projects) { + let results = {}; + try{ + if(projects){ + await this.getResourceTypeList() + .then(resourcetypes =>{ + results.resourcetypes = resourcetypes; + }) + .then( async ()=>{ + for(const project of projects){ + for(const id of project.project_quota_ids){ + await ProjectService.getProjectQuota(id).then(async quota =>{ + await this.getResourceUnitType(quota.resource_type_id, results.resourcetypes) + .then(resourceType =>{ + project[quota.resource_type_id] = UnitConverter.getUIResourceUnit(resourceType, quota.value) + }) + }) + } + projects.map((pro,index) => { + if(pro.name === project.name){ + project['actionpath']= '/project/view'; + projects[index] = project; + } + }) + } + }) + results.projects = projects; + return results.projects; + } + } catch (error) { + console.error('[project.services.getUpdatedProjectQuota]',error); + } + return results; + }, + + getProjectQuota: async function(id) { + try { + const response = await axios.get('/api/project_quota/'+id); + return response.data; + } catch (error) { + console.error('[project.services.getProjectQuota]',error); + } + }, + getResourceTypeList: async function() { + try{ + const response = await axios.get('/api/resource_type/'); + return response.data.results; + } catch (error) { + console.error('[project.services.getResourceTypeList]',error); + } + }, + getResourceType: async function(id){ + try{ + const response = await axios.get('/api/resource_type/'+id); + return response.data; + }catch(error){ + console.error('[project.services.getResourceType]',error); + } + }, +} + +export default ProjectService; \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js new file mode 100644 index 0000000000000000000000000000000000000000..cda5342a5c616ea178c7a7ed0d8a7508236daeed --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js @@ -0,0 +1,114 @@ +import axios from 'axios' +import _ from 'lodash'; + +axios.defaults.headers.common['Authorization'] = 'Basic dGVzdDp0ZXN0'; + +const ScheduleService = { + getSchedulingUnitDraft: async function (){ + let res = []; + await axios.get('/api/scheduling_unit_draft/?ordering=id') + .then(response => { + res= response; + }).catch(function(error) { + console.error('[schedule.services.getSchedulingUnitDraft]',error); + }); + return res; + }, + getSchedulingUnitDraftById: async function (id){ + let res = []; + await axios.get('/api/scheduling_unit_draft/'+id) + .then(response => { + res= response; + }).catch(function(error) { + console.error('[schedule.services.getSchedulingUnitDraftById]',error); + }); + return res; + }, + getScheduleTasksBySchedulingUnitId: async function(id){ + let scheduletasklist=[]; + let taskblueprints = []; + // Common keys for Task and Blueprint + let commonkeys = ['id','created_at','description','name','tags','updated_at','url','do_cancel','relative_start_time','relative_stop_time','start_time','stop_time','duration']; + await this.getTaskBlueprints().then( blueprints =>{ + taskblueprints = blueprints.data.results; + }) + await this.getTasksDraftBySchedulingUnitId(id) + .then(response =>{ + for(const task of response.data.results){ + let scheduletask = []; + scheduletask['tasktype'] = 'Task Draft'; + scheduletask['actionpath'] = '/task/view/draft/'+task['id']; + scheduletask['blueprint_draft'] = task['task_blueprints']; + + //fetch task draft details + for(const key of commonkeys){ + scheduletask[key] = task[key]; + } + + //Fetch blueprint details for Task Draft + let filteredblueprints = _.filter(taskblueprints, function(o) { + if (o.draft_id === task['id']) return o; + }); + + for(const blueprint of filteredblueprints){ + let taskblueprint = []; + taskblueprint['tasktype'] = 'Blueprint'; + taskblueprint['actionpath'] = '/task/view/blueprint/'+blueprint['id']; + taskblueprint['blueprint_draft'] = blueprint['draft']; + for(const key of commonkeys){ + taskblueprint[key] = blueprint[key]; + } + //Add Blue print details to array + scheduletasklist.push(taskblueprint); + } + //Add Task Draft details to array + scheduletasklist.push(scheduletask); + } + }).catch(function(error) { + console.error('[schedule.services.getScheduleTasksBySchedulingUnitId]',error); + }); + return scheduletasklist; + }, + getTaskBlueprints: async function (){ + let res=[]; + await axios.get('/api/task_blueprint/?ordering=id') + .then(response => { + res= response; + }).catch(function(error) { + console.error('[schedule.services.getTaskBlueprints]',error); + }); + return res; + }, + getTaskBlueprintByTaskDraftId: async function (id){ + let res=[]; + await axios.get('/api/task_draft/'+id+'/task_blueprint/?ordering=id') + .then(response => { + res= response; + }).catch(function(error) { + console.error('[schedule.services.getTaskBlueprintByTaskDraftId]',error); + }); + return res; + }, + getTasksDraftBySchedulingUnitId: async function (id){ + let res=[]; + await axios.get('/api/scheduling_unit_draft/'+id+'/task_draft/?ordering=id') + .then(response => { + res= response; + }).catch(function(error) { + console.error('[schedule.services.getTasksDraftBySchedulingUnitId]',error); + }); + return res; + }, + getBlueprintsByschedulingUnitId: async function (id){ + let res=[]; + await axios.get('/api/scheduling_unit_draft/'+id+'/scheduling_unit_blueprint/?ordering=id') + .then(response => { + res= response; + }).catch(function(error) { + console.error('[schedule.services.getBlueprintsByschedulingUnitId]',error); + }); + return res; + }, +} + +export default ScheduleService; \ No newline at end of file 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 255376dd71e70a316aa6cd48fc77dc2bb9ee5eb1..9ecfcc3c61e4a0bcfc9c89d112e43deb472e8ce1 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js @@ -10,8 +10,17 @@ const UnitConverter = { getDBResourceUnit: function() { }, - getUIResourceUnit: function() { - + 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); + } + return value } };