From 15f8a6a70ba06b97faf89197f6b8cbcf2693d1bd Mon Sep 17 00:00:00 2001
From: Muthukrishnanmatriot
 <76949556+muthukrishnanmatriot@users.noreply.github.com>
Date: Mon, 15 Mar 2021 10:39:56 +0530
Subject: [PATCH] TMSS-213 - Implemented Find Object

---
 SAS/TMSS/frontend/tmss_webapp/src/App.js      |  26 ++-
 .../src/layout/components/AppTopbar.js        |   9 +-
 .../src/layout/components/FindObejct.js       |  76 +++++++
 .../tmss_webapp/src/layout/sass/_content.scss |   5 +
 .../tmss_webapp/src/layout/sass/_topbar.scss  |   9 +
 .../src/routes/Search/find.object.result.js   | 210 ++++++++++++++++++
 .../tmss_webapp/src/routes/Search/index.js    |   3 +
 .../frontend/tmss_webapp/src/routes/index.js  |   7 +
 8 files changed, 338 insertions(+), 7 deletions(-)
 create mode 100644 SAS/TMSS/frontend/tmss_webapp/src/layout/components/FindObejct.js
 create mode 100644 SAS/TMSS/frontend/tmss_webapp/src/routes/Search/find.object.result.js
 create mode 100644 SAS/TMSS/frontend/tmss_webapp/src/routes/Search/index.js

diff --git a/SAS/TMSS/frontend/tmss_webapp/src/App.js b/SAS/TMSS/frontend/tmss_webapp/src/App.js
index 12ca8a5aa71..0dfefd73671 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/App.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/App.js
@@ -31,7 +31,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);
@@ -40,6 +41,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'},
@@ -127,6 +129,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"
+        });
+    }
+
     render() {
         const wrapperClass = classNames('layout-wrapper', {
             'layout-overlay': this.state.layoutMode === 'overlay',
@@ -146,12 +161,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, state:{objectType: this.state.objectType, findObjectId: this.state.findObjectId}}}   />}
                                 <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 f112943d779..858538c68d0 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 { FindObejct } from './FindObejct';
 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>
                         }
+                       <FindObejct setSearchField={this.props.setSearchField} />
                     </div>
                         
                 </div>
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/components/FindObejct.js b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/FindObejct.js
new file mode 100644
index 00000000000..c6f261798ec
--- /dev/null
+++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/FindObejct.js
@@ -0,0 +1,76 @@
+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';
+
+export class FindObejct extends Component {
+
+    constructor(props) {
+        super(props);
+        this.state = {
+            // Find Object - dropdown list value
+            objectTypes: [
+                {name: 'Sub Task', code: 'subtask'},
+                {name: 'Task Blueprint', code: 'taskblueprint'},
+                {name: 'Task Draft', code: 'taskdraft'},
+                {name: 'SU Blueprint', code: 'sublueprint'},
+                {name: 'SU Draft', code: 'sudraft'},
+                {name: 'Project', code: 'project'},
+            ],
+            findObjectPlaceholder: 'Sub Task',
+            objectid: ''
+        };
+        this.findObject = this.findObject.bind(this);
+        this.setFindObject = this.setFindObject.bind(this);
+    }
+
+    /**
+     * Set Object Type & Search Object id value
+     * @param {*} value 
+     */
+    setFindObject(value) {
+        if (value.name) {
+            this.setState({findObjectPlaceholder: value.name, objectid: this.state.objectid})
+        }   else if(this.state.findObjectPlaceholder === 'Project') {
+            this.setState({objectid: value});
+        } else if (!isNaN(value)) {
+            this.setState({objectid: value});
+        }
+    }
+        
+    /**
+     * Callback function to find Object
+     */
+    findObject() {
+        let objectType = _.find(this.state.objectTypes, {name: this.state.findObjectPlaceholder })
+        if (this.state.objectid && this.state.objectid.length > 0) {
+            this.props.setSearchField(objectType.code, this.state.objectid);
+        }   else {
+            appGrowl.show({severity: 'info', summary: 'Information', detail: 'Enter Object id to search'});
+        }
+    }
+
+    render() {
+        return (
+            <React.Fragment>
+                <Growl ref={(el) => setAppGrowl(el)} />
+                <div className="top-right-bar find-object-search" style={{marginRight: '2em'}}>
+                    <span className="p-input-icon-right"  >
+                        <a href="#" > <i className="pi pi-search find-object-search-btn" 
+                        onClick={this.findObject} /> </a>
+                        <Dropdown  
+                            className="p-link layout-menu-button" 
+                            value={this.state.objectid} 
+                            options={this.state.objectTypes}   
+                            optionLabel="name"  
+                            editable 
+                            placeholder={this.state.findObjectPlaceholder}
+                            onChange={(e) => {this.setFindObject(e.value)}}
+                        /> 
+                    </span>
+                </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 5c49ad86c0d..16fda99097d 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 6c190d5e90b..b735f26abc1 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_topbar.scss
+++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_topbar.scss
@@ -143,4 +143,13 @@
 
 .top-right-bar button {
     padding-left: 5px;
+}
+.find-object-search {
+    padding-top: 0px;
+}
+.find-object-search-btn {
+    margin-left: 10px;
+    float: right;
+    margin-top: .3em;
+    color: white;
 }
\ 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 00000000000..6b190a03e36
--- /dev/null
+++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Search/find.object.result.js
@@ -0,0 +1,210 @@
+import React, {Component} from 'react';
+import PageHeader from '../../layout/components/PageHeader';
+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 = {
+            findObjectId: '',
+            objNodes: [],
+        };
+        this.schedulingSetList= {};
+        this.projectsList= {};
+        this.data= {};
+    }
+
+    /**
+     * Find Object based in search id
+     */
+    async componentDidMount(){
+        const objectType = (this.props.location.state && this.props.location.state.objectType)?this.props.location.state.objectType:'';
+        if (objectType === 'subtask') {
+            let subtaskDetails = await this.findSubTask(this.props.location.state.findObjectId);
+            this.setState({objNodes: (subtaskDetails)?[this.data.subtask]:[]});
+        }   
+        else if (objectType === 'taskdraft') {
+            let taskDetails = await this.findTask('draft', this.props.location.state.findObjectId);
+            this.setState({objNodes: (taskDetails)?[this.data.task]:[]});
+        }   
+        else if (objectType === 'taskblueprint') {
+            let taskDetails = await this.findTask('blueprint', this.props.location.state.findObjectId);
+            this.setState({objNodes: (taskDetails)?[this.data.task]:[]});
+        }   
+        else if (objectType === 'sublueprint') {
+            let suDetails = await this.findSchedulingUnit('blueprint', this.props.location.state.findObjectId);
+            this.setState({objNodes: (suDetails)?[this.data.schedulingUnit]:[]});
+        }   
+        else if (objectType === 'sudraft') {
+            let suDetails = await this.findSchedulingUnit('draft', this.props.location.state.findObjectId);
+            this.setState({objNodes: (suDetails)?[this.data.schedulingUnit]:[]});
+        }   
+        else if (objectType === 'project') {
+            let projectDetails = await this.findProject(this.props.location.state.findObjectId);
+            this.setState({objNodes: (projectDetails)?[this.data.project]:[]});
+        }
+        else {
+            this.setState({objNodes: []});
+        }
+    }
+
+    /**
+     * Find SubTask for given id
+     * @param {*} id 
+     * @returns 
+     */
+    async findSubTask(id){
+        const subtaskDetails  = await TaskService.getSubtaskDetails(id);
+        if (subtaskDetails) {
+            await this.findTask('blueprint', subtaskDetails.task_blueprint_id);
+            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="pi pi-info-circle" /></a></span></>;
+            subtask['icon'] = 'pi pi-fw pi-calendar';
+            subtask['children'] = [this.data.task];
+            this.data['subtask'] = subtask;
+        }
+        return subtaskDetails;
+    }
+
+    /**
+     * Find Task details for given id
+     * @param {*} taskType 
+     * @param {*} id 
+     * @returns 
+     */
+    async findTask(taskType, id){
+        const taskDetails  = await TaskService.getTaskDetails(taskType, id);
+        if (taskDetails) {
+            if (taskType === 'blueprint') {
+                await this.findSchedulingUnit('blueprint', taskDetails.scheduling_unit_blueprint_id);
+            }   else {
+                await this.findSchedulingUnit('draft', taskDetails.scheduling_unit_draft_id);
+            }
+            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="pi pi-info-circle" /></a></span></>;
+            task['icon'] = 'pi pi-fw pi-calendar';
+            task['children'] = [this.data.schedulingUnit];
+            this.data['task'] = task;
+            this.setState({objNodes: [this.data.task]});
+        }
+        return taskDetails;
+    }
+
+    /**
+     * 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) {
+            await this.findProjectBySUId(suDetails.id);
+            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="pi pi-info-circle" /></a></span></>;
+            schedulingUnit['icon'] = 'pi pi-fw pi-calendar';
+            schedulingUnit['children'] = [this.data.suSet];
+            this.data['schedulingUnit'] = schedulingUnit;
+            this.setState({objNodes: [this.data.schedulingUnit]});
+        }
+        return suDetails;
+    }
+
+    /**
+     * Find project for given SU id 
+     * @param {*} suId 
+     */
+    async findProjectBySUId(suId) {
+        this.schedulingSetList = await ScheduleService.getSchedulingSets();
+        this.projectsList = await ScheduleService.getProjectList();
+        const suSetDetails = this.schedulingSetList.find((suSet) => { return  suId === suSet.id });
+        if (suSetDetails) {
+            const project = this.projectsList.find((project) => { return suSetDetails.project_id === project.name});
+            this.getProjectDetails(project);
+            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="pi pi-info-circle" /></a></span></>;
+            suSet['icon'] = 'fab fa-fw fa-wpexplorer';
+            suSet['children'] = [this.data.project];
+            this.data['suSet'] = suSet;
+            this.setState({objNodes: [this.data.suSet]});
+        }
+    }
+
+    /**
+     * Prepare Project details for tree view
+     * @param {*} projectDetails 
+     */
+    getProjectDetails(projectDetails) {
+        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="pi pi-info-circle" /></a></span></>;
+            project['icon'] = 'fab fa-fw fa-wpexplorer';
+            this.data['project'] = project;
+            this.setState({objNodes: [this.data.project]});
+        }
+    }
+
+    /**
+     * Find project details for given id
+     * @param {*} id 
+     * @returns 
+     */
+    async findProject(id){
+        const projectDetails = await ProjectService.getProjectDetails(id);
+        this.getProjectDetails(projectDetails);
+        return projectDetails;
+    }
+
+    render(){
+        return(
+            <>
+               <PageHeader location={this.props.location} title={'Find Object'} 
+                    actions={[]}
+                />
+                {this.state.objNodes.length>0 &&
+                    <>
+                        <Tree value={this.state.objNodes} selectionMode="single" expandedKeys={true} style={{width: 'auto'}} />
+                    </>
+                }
+                {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 00000000000..fcfd0526ca2
--- /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 c296c76ff16..4f862988865 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",
+        component: FindObjectResult,
+        name: 'Find Object',
+        title: 'Find Object'
     }
 ];
 
-- 
GitLab