diff --git a/SAS/TMSS/frontend/tmss_webapp/src/App.js b/SAS/TMSS/frontend/tmss_webapp/src/App.js index ff0415792cc95c427b1b5890ce34bbe66bb25316..f7800c0f6da31b54be802cfb909310de8fc8a4f3 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/App.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/App.js @@ -34,7 +34,8 @@ class App extends Component { overlayMenuActive: localStorage.getItem('overlayMenuActive') === 'true' ? true : false, mobileMenuActive: localStorage.getItem('mobileMenuActive') === 'true' ? true : false, authenticated: Auth.isAuthenticated(), - redirect: (Auth.isAuthenticated() && window.location.pathname === "/login")?"/":window.location.pathname + redirect: (Auth.isAuthenticated() && window.location.pathname === "/login")?"/":window.location.pathname, + findObjectPlaceholder: 'Sub Task', }; this.onWrapperClick = this.onWrapperClick.bind(this); this.onToggleMenu = this.onToggleMenu.bind(this); @@ -43,6 +44,7 @@ class App extends Component { this.setPageTitle = this.setPageTitle.bind(this); this.loggedIn = this.loggedIn.bind(this); this.logout = this.logout.bind(this); + this.setSearchField = this.setSearchField.bind(this); this.menu = [ {label: 'Dashboard', icon: 'pi pi-fw pi-home', to:'/dashboard',section: 'dashboard'}, {label: 'Cycle', icon:'pi pi-fw pi-spinner', to:'/cycle',section: 'cycle'}, @@ -131,6 +133,19 @@ class App extends Component { this.setState({authenticated: false, redirect:"/"}); } + /** + * Set search param + * @param {*} key + * @param {*} value + */ + setSearchField(key, value) { + this.setState({ + objectType: key, + findObjectId: value, + redirect:"/find/object/"+key+"/"+value + }); + } + render() { const wrapperClass = classNames('layout-wrapper', { 'layout-overlay': this.state.layoutMode === 'overlay', @@ -151,12 +166,17 @@ class App extends Component { {/* Load main routes and application only if the application is authenticated */} {this.state.authenticated && <> - <AppTopbar onToggleMenu={this.onToggleMenu} isLoggedIn={this.state.authenticated} onLogout={this.logout}></AppTopbar> + <AppTopbar + onToggleMenu={this.onToggleMenu} + isLoggedIn={this.state.authenticated} + onLogout={this.logout} + setSearchField={this.setSearchField} + /> <Router basename={ this.state.currentPath }> <AppMenu model={this.menu} onMenuItemClick={this.onMenuItemClick} layoutMode={this.state.la} active={this.state.menuActive}/> <div className="layout-main"> {this.state.redirect && - <Redirect to={{pathname: this.state.redirect}} />} + <Redirect to={{pathname: this.state.redirect }}/> } <AppBreadCrumbWithRouter setPageTitle={this.setPageTitle} /> <RoutedContent /> </div> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/components/AppTopbar.js b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/AppTopbar.js index f112943d779cdedc9448a0f7ff2f42ce10fab3c2..6625eb1ea1cb57c76a93d7f35e14ce598edd2a98 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/components/AppTopbar.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/AppTopbar.js @@ -5,19 +5,17 @@ import 'primereact/resources/themes/nova-light/theme.css'; import 'primereact/resources/primereact.css'; import 'primeflex/primeflex.css'; import { PropTypes } from 'prop-types'; - import Auth from '../../authenticate/auth'; - +import { FindObject } from './FindObject'; export class AppTopbar extends Component { constructor(props) { super(props); this.state = { - username: Auth.getUser().name + username: Auth.getUser().name, }; } - static defaultProps = { onToggleMenu: null } @@ -31,9 +29,11 @@ export class AppTopbar extends Component { <React.Fragment> <div className="layout-wrapper layout-static layout-static-sidebar-inactive"> <div className="layout-topbar clearfix"> + <button className="p-link layout-menu-button" onClick={this.props.onToggleMenu}> <i className="pi pi-bars"></i></button> <span className="header-title">TMSS</span> + {this.props.isLoggedIn && <div className="top-right-bar"> <span><i className="fa fa-user"></i>{this.state.username}</span> @@ -41,6 +41,7 @@ export class AppTopbar extends Component { <i className="pi pi-power-off"></i></button> </div> } + <FindObject setSearchField={this.props.setSearchField} /> </div> </div> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/components/FindObject.js b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/FindObject.js new file mode 100644 index 0000000000000000000000000000000000000000..530ba4d002023dce0d7dacdd940e4d5db175c50e --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/FindObject.js @@ -0,0 +1,109 @@ +import React, {Component} from 'react'; +import { Dropdown } from 'primereact/dropdown'; +import _ from 'lodash'; +import { appGrowl , setAppGrowl } from './AppGrowl'; +import { Growl } from 'primereact/components/growl/Growl'; +import { InputText } from 'primereact/inputtext'; + +export class FindObject extends Component { + + constructor(props) { + super(props); + this.state = { + // Find Object - dropdown list value + objectTypes: [ + {name: 'Scheduling Unit', code: 'sublueprint'}, + {name: 'Task', code: 'taskblueprint'}, + {name: 'Subtask', code: 'subtask'}, + // {name: 'Task Draft', code: 'taskdraft'}, + //{name: 'SU Draft', code: 'sudraft'}, + // {name: 'Project', code: 'project'}, + ], + objectId: '', + objectType: {name: 'Scheduling Unit', code: 'sublueprint'} + }; + this.findObject = this.findObject.bind(this); + this.setObjectType = this.setObjectType.bind(this); + this.setFindObjectId = this.setFindObjectId.bind(this); + this.handleEvent = this.handleEvent.bind(this); + } + + /** + * + * @param {Key Event} e - Key code + */ + handleEvent(e) { + var key = e.which || e.keyCode; + if(key === 13 || key === 'Enter') { + this.findObject(); + } + } + + /** + * Set Object Type + * @param {String} value - Object type value + */ + setObjectType(value) { + if (value.name && value.name === 'Project') { + this.setState({objectType: value}); + } else if(isNaN(this.state.objectId)){ + this.setState({objectType: value, objectId: ''}); + } else { + this.setState({objectType: value}); + } + } + + /** + * Set Object id value + * @param {String/Number} value - Object id, accepts alphanumeric if object type is 'Project' + */ + setFindObjectId(value) { + if (this.state.objectType.name === 'Project' || !isNaN(value)) { + this.setState({objectId: value}); + } else{ + appGrowl.show({severity: 'info', summary: 'Information', detail: 'Enter valid object Id'}); + } + } + + /** + * Callback function to find Object + */ + findObject() { + if (this.state.objectId && this.state.objectId.length > 0) { + this.props.setSearchField(this.state.objectType.code, this.state.objectId); + } else { + appGrowl.show({severity: 'info', summary: 'Information', detail: 'Enter Object Id'}); + } + } + + render() { + return ( + <React.Fragment> + <Growl ref={(el) => setAppGrowl(el)} /> + <div className="top-right-bar find-object-search" style={{marginRight: '1em'}}> + <Dropdown + className="p-link layout-menu-button find-object-type" + value={this.state.objectType} + options={this.state.objectTypes} + optionLabel="name" + onChange={(e) => {this.setObjectType(e.value)}} + /> + + + <InputText + value={this.state.objectId} + onChange={(e) => {this.setFindObjectId(e.target.value)}} + title='Enter Object Id to search Object' + className="find-object-search-input" + placeholder="Search by ID" + onKeyDown={this.handleEvent} + /> + <button className="p-link layout-menu-button" style={{float: 'right'}} onClick={this.findObject} > + <i className="pi pi-search find-object-search-btn" /> + </button> + + </div> + </React.Fragment> + ); + } +} diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_content.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_content.scss index 5c49ad86c0d840f2b6876fd5662d5ca981e34331..16fda99097df01c69485d146d9d0bb3940775d50 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_content.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_content.scss @@ -3,4 +3,9 @@ padding: 60px 16px 16px 25px; min-height: 95vh; background-color: white; +} + +.find-obj-tree-view { + margin-left: 1em; + margin-right: 1em; } \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_topbar.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_topbar.scss index 6c190d5e90b4061687c6da38aa6fdc6f3246ccfb..a7a0ff6d53998a391bc5b943bc20ba5f4b133170 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_topbar.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_topbar.scss @@ -118,9 +118,10 @@ color: $topbarItemColor; @include transition(color $transitionDuration); - span { + // Search type dropdown arrow looks bigger in topbar, + /* span { font-size: 2em; - } + }*/ &:hover { color: $topbarItemHoverColor; @@ -143,4 +144,30 @@ .top-right-bar button { padding-left: 5px; -} \ No newline at end of file +} + +.find-object-search { + padding-top: 0px; + +} + +.find-object-search-input { + border-inline-start-width: 0px; + border-inline-end-width: 2em !important; + width: 11em; +} + +.find-object-search-btn { + display: inline-block; + right: 27px; + position: relative; + top: 6px; + color: darkblue; +} + +.find-object-type { + width: 12em; + right:1em; +} + + \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Search/find.object.result.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Search/find.object.result.js new file mode 100644 index 0000000000000000000000000000000000000000..d341e5e30893b8fee51cdd2e253028804c5949a8 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Search/find.object.result.js @@ -0,0 +1,231 @@ +import React, {Component} from 'react'; +import PageHeader from '../../layout/components/PageHeader'; +import AppLoader from '../../layout/components/AppLoader'; +import { Tree } from 'primereact/tree'; +import TaskService from './../../services/task.service'; +import ScheduleService from './../../services/schedule.service'; +import ProjectService from './../../services/project.service'; + +export class FindObjectResult extends Component{ + constructor(props){ + super(props); + this.state = { + objNodes: [], + expandedKeys: {}, + isLoading: true + }; + this.schedulingSetList= {}; + this.projectsList= {}; + this.data= {}; + this.expandAll = this.expandAll.bind(this); + this.expandNode = this.expandNode.bind(this); + } + + + componentDidUpdate(prevProps, prevState) { + const objectType = this.props.match.params.type; + const objectId = this.props.match.params.id; + const prevObjectType = prevProps.match.params.type; + const prevObjectId = prevProps.match.params.id; + if(objectType !== prevObjectType || objectId !== prevObjectId){ + this.findObject(); + } + } + + componentDidMount(){ + this.findObject(); + } + + /** + * Find Object based in search id + */ + async findObject(){ + let objNodes = []; + this.setState({objNodes: objNodes, isLoading: true}); + const objectType = this.props.match.params.type;//(this.props.location.state && this.props.location.state.objectType)?this.props.location.state.objectType:''; + const objectid = this.props.match.params.id; + if (objectType === 'subtask') { + objNodes = await this.findSubTask(objectid); + } + else if (objectType === 'taskdraft') { + objNodes = await this.findTask('draft', objectid); + } + else if (objectType === 'taskblueprint') { + objNodes = await this.findTask('blueprint', objectid); + } + else if (objectType === 'sublueprint') { + objNodes = await this.findSchedulingUnit('blueprint', objectid); + } + else if (objectType === 'sudraft') { + objNodes = await this.findSchedulingUnit('draft', objectid); + } + else if (objectType === 'project') { + objNodes = await this.findProject(objectid); + } + this.setState({objNodes: objNodes, isLoading: false}); + this.expandAll(); + } + + /** + * Find SubTask for given id + * @param {*} id + * @returns + */ + async findSubTask(id){ + const subtaskDetails = await TaskService.getSubtaskDetails(id); + if (subtaskDetails) { + let subtask = {}; + subtask['key'] = 'subtask'+subtaskDetails.id; + subtask['label'] = <> SubTask ({subtaskDetails.id}) + {/* -- View page not available yet -- + <span className="find-obj-tree-view"><a href="" target='_blank'>View</a></span> */} + <span className="find-obj-tree-view"> <a href={subtaskDetails.url} target='_blank' + title=" View SubTask API"><i className="fa fa-link" /></a></span></>; + subtask['icon'] = 'fas fa-tasks'; + subtask['children'] = await this.findTask('blueprint', subtaskDetails.task_blueprint_id); + return [subtask]; + } + return ''; + } + + /** + * Find Task details for given id + * @param {*} taskType + * @param {*} id + * @returns + */ + async findTask(taskType, id){ + const taskDetails = await TaskService.getTask(taskType, id); + if (taskDetails) { + let task = {}; + task['key'] = 'task'+taskDetails.id; + task['label'] = <> Task ({taskDetails.id}) + <span className="find-obj-tree-view"> + <a href={`/task/view/${taskType}/${taskDetails.id}`} target='_blank' title=" View Task Details"> + <i className="fa fa-eye" /> + </a> + </span> + <span> <a href={taskDetails.url} target='_blank' title=" View Task API"><i className="fa fa-link" /></a></span></>; + task['icon'] = 'fa fa-tasks'; + if (taskType === 'blueprint') { + task['children'] = await this.findSchedulingUnit('blueprint', taskDetails.scheduling_unit_blueprint_id); + } else { + task['children'] = await this.findSchedulingUnit('draft', taskDetails.scheduling_unit_draft_id); + } + return [task]; + } + return ''; + } + + /** + * Find Scheduling Unit for given id + * @param {*} suType + * @param {*} id + * @returns + */ + async findSchedulingUnit(suType, id){ + let suDetails = null; + if (suType === 'blueprint') { + suDetails = await ScheduleService.getSchedulingUnitBlueprintById (id); + } else { + suDetails = await ScheduleService.getSchedulingUnitDraftById(id); + } + if (suDetails) { + let schedulingUnit = {}; + schedulingUnit['key'] = 'su'+suDetails.id; + schedulingUnit['label'] = <> Scheduling Unit ({suDetails.id}) + <span className="find-obj-tree-view"><a href={`/schedulingunit/view/${suType}/${suDetails.id}`} + target='_blank' title=" View Scheduling Unit Details"><i className="fa fa-eye" /></a> </span> + <span><a href={suDetails.url} target='_blank' title=" View Scheduling Unit API" > + <i className="fa fa-link" /></a></span></>; + schedulingUnit['icon'] = 'pi pi-fw pi-calendar'; + schedulingUnit['children'] = await this.findSchedulingSetBySUId(suDetails); + return [schedulingUnit]; + } + return ''; + } + + /** + * Find project for given SU id + * @param {*} suId + */ + async findSchedulingSetBySUId(suDetails) { + const suSetDetails = suDetails.scheduling_set_object; + if (suSetDetails) { + let suSet = {}; + suSet['key'] = 'suset'+suSetDetails.id; + suSet['label'] = <> Scheduling Set ({suSetDetails.id}) + {/* -- View page not available yet -- + <span className="find-obj-tree-view"><a href="" + target='_blank' title='View Project details'><i className="fa fa-eye" /></a></span> */} + <span className="find-obj-tree-view"> + <a href={suSetDetails.url} target='_blank' title='View Scheduling Set API'><i className="fa fa-link" /></a></span></>; + suSet['icon'] = 'fa fa-table'; + suSet['children'] = await this.findProject(suSetDetails.project_id); + return [suSet]; + } + return ''; + } + + /** + * Find project details for given id + * @param {*} id + * @returns + */ + async findProject(id){ + const projectDetails = await ProjectService.getProjectDetails(id); + if (projectDetails) { + let project = {}; + project['key'] = projectDetails.name; + project['label'] = <> Project ({projectDetails.name}) + <span className="find-obj-tree-view"><a href={`/project/view/${projectDetails.name}`} + target='_blank' title='View Project details'><i className="fa fa-eye" /></a></span> + <span><a href={projectDetails.url} target='_blank' title='View Project API'><i className="fa fa-link" /></a></span></>; + project['icon'] = 'fab fa-fw fa-wpexplorer'; + return [project]; + } + return ''; + } + + + expandNode(node, expandedKeys) { + if (node.children && node.children.length) { + expandedKeys[node.key] = true; + + for (let child of node.children) { + this.expandNode(child, expandedKeys); + } + } + } + + expandAll() { + let expandedKeys = {}; + for (let node of this.state.objNodes) { + this.expandNode(node, expandedKeys); + } + this.setState({expandedKeys: expandedKeys }); + } + + render(){ + return( + <> + <PageHeader location={this.props.location} title={'Search Result'} + actions={[]} + /> + { this.state.isLoading ? <AppLoader /> : + <> + {this.state.objNodes.length > 0 && + <> + <Tree value={this.state.objNodes} selectionMode="multiple" expandedKeys={this.state.expandedKeys} + style={{width: 'auto'}} onToggle={e => this.setState({expandedKeys: e.value})} /> + </> + } + {this.state.objNodes.length === 0 && + <> No Object found ! </> + } + </> + } + </> + ) + } +} diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Search/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Search/index.js new file mode 100644 index 0000000000000000000000000000000000000000..fcfd0526ca2aec256d95352823d62bab13b9e8a5 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Search/index.js @@ -0,0 +1,3 @@ +import {FindObjectResult} from './find.object.result'; + +export {FindObjectResult} ; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js index 0ef279ba06cec4b9353abee24fa5dff9b3fa7eba..286d0c21dd0a1a496adfb6f1be8a519bd5effdd9 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js @@ -15,6 +15,7 @@ import SchedulingUnitCreate from './Scheduling/create'; import EditSchedulingUnit from './Scheduling/edit'; import { CycleList, CycleCreate, CycleView, CycleEdit } from './Cycle'; import { TimelineView, WeekTimelineView, ReservationCreate, ReservationList } from './Timeline'; +import { FindObjectResult } from './Search/' import SchedulingSetCreate from './Scheduling/excelview.schedulingset'; import Workflow from './Workflow'; import { Growl } from 'primereact/components/growl/Growl'; @@ -165,6 +166,12 @@ export const routes = [ component: ReservationCreate, name: 'Reservation Add', title: 'Reservation - Add' + }, + { + path: "/find/object/:type/:id", + component: FindObjectResult, + name: 'Find Object', + title: 'Find Object' } ]; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js index 4e818ff260faad3939ebef7e04da33df30ddaf3f..fd4b6d769ecc53b022be3da580317ebbae11a24d 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js @@ -2,31 +2,39 @@ const axios = require('axios'); const TaskService = { getTaskDetails: async function (taskType, taskId) { - try { - const url = taskType === 'blueprint'? '/api/task_blueprint/': '/api/task_draft/'; - const response = await axios.get(url + taskId); - response.data.predecessors = []; - response.data.successors = []; - if (taskType === 'blueprint') { - response.data.blueprints = []; - } else { - response.data.draftName = null; - } - return this.getTaskRelationsByTask(taskType, response.data) - .then(relations => { - response.data.predecessors = relations.predecessors; - response.data.successors = relations.successors; - if (taskType === 'draft') { - response.data.blueprints = relations.blueprints; - } else { - response.data.draftObject = relations.draft; - } - return response.data; - }); - - } catch (error) { - console.error(error); + try { + const responseData = await this.getTask(taskType, taskId); + responseData.predecessors = []; + responseData.successors = []; + if (taskType === 'blueprint') { + responseData.blueprints = []; + } else { + responseData.draftName = null; } + return this.getTaskRelationsByTask(taskType, responseData) + .then(relations => { + responseData.predecessors = relations.predecessors; + responseData.successors = relations.successors; + if (taskType === 'draft') { + responseData.blueprints = relations.blueprints; + } else { + responseData.draftObject = relations.draft; + } + return responseData; + }); + + } catch (error) { + console.error(error); + } + }, + getTask : async function (taskType, taskId) { + try { + const url = taskType === 'blueprint'? '/api/task_blueprint/': '/api/task_draft/'; + const response = await axios.get(url + taskId); + return response.data; + } catch (error) { + console.error(error); + } }, getTaskTemplate: async function(templateId) { try {