From 2e2179b8b212756ae90f99fb9c95e6a1ef0ccb19 Mon Sep 17 00:00:00 2001
From: rbokhorst <rbokhorst@astron.nl>
Date: Wed, 26 Dec 2018 10:36:04 +0000
Subject: [PATCH] OSB-35: fix table height and re-render

---
 .gitattributes                                |  1 +
 .../src/components/FillHeight.js              | 88 +++++++++++++++++
 .../src/components/StationTestView.js         | 95 ++++++++++++-------
 .../src/components/Toolbar.js                 |  1 +
 .../src/pages/StationOverviewPage.js          | 91 +++++++++++-------
 .../maintenancedb_view/src/pages/TilesPage.js |  8 +-
 .../src/themes/lofar-styles.css               |  4 +
 .../src/themes/lofar-styles.scss              |  6 ++
 .../maintenancedb_view/src/themes/lofar.css   |  4 +
 9 files changed, 230 insertions(+), 68 deletions(-)
 create mode 100644 LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillHeight.js

diff --git a/.gitattributes b/.gitattributes
index c18094c8f08..37dd22f583a 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1850,6 +1850,7 @@ LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.test.js -text
 LCU/Maintenance/MDB_WebView/maintenancedb_view/src/api_configuration.js -text
 LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AntennaErrorDetails.js -text
 LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AntennaView.js -text
+LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillHeight.js -text
 LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.css -text
 LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.js -text
 LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.scss -text
diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillHeight.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillHeight.js
new file mode 100644
index 00000000000..f36a94934af
--- /dev/null
+++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillHeight.js
@@ -0,0 +1,88 @@
+import React from "react";
+
+/*
+ * FillHeight: Creates a div that fills the remaining height of the viewport.
+ * Window resize is monitored to scale the div accordingly.
+ *
+ * This component uses the 'render prop' pattern where the 'children' prop is used
+ * as the render function. The height of the div (in an object) is passed to
+ * the function for use by subcomponents. E.g.:
+ *
+ *  <FillHeight>
+ *      { (props) => <MyComponent hgt={props.height} /> }
+ *  </FillHeight>
+ */
+class FillHeight extends React.Component {
+
+    static defaultProps = {
+        className: 'fill-height-container',  // classname for the container div
+        gutterBottom: 10,  // px, leave some space at the bottom
+        minHeight: 200     // px, minimum height
+    };
+
+    state = {
+        height: 500        // px, height of the container div
+    };
+
+    node = null;           // internal ref to the DOM node (div)
+    mounted = false;       // bool
+    resizeTimeout = null;  // throttling of render during resize
+
+
+    componentDidMount() {
+        this.mounted = true;
+
+        window.addEventListener("resize", () => {
+            window.clearTimeout(this.resizeTimeout);
+            this.resizeTimeout = window.setTimeout(this.onWindowResize, 200);
+        });
+
+        // force a render now that it has been mounted
+        // (the offset is known at this point)
+        this.onWindowResize();
+    }
+
+    componentWillUnmount() {
+        this.mounted = false;
+        window.removeEventListener("resize", this.onWindowResize);
+    }
+
+    onWindowResize = () => {
+        if (!this.mounted || !this.node) {
+            return;
+        }
+
+        let offset = this.node.getBoundingClientRect().top;
+        let height = window.innerHeight - offset - this.props.gutterBottom;
+        if (height < this.props.minHeight) {
+            height = this.props.minHeight;
+        }
+
+        this.setState({
+            height
+        });
+    };
+
+    getNodeRef = (node) => {
+        this.node = node;
+    }
+
+    render() {
+        // Create a simple div for measuring the viewport offset when it mounts,
+        // then re-render.
+        if (!this.mounted) {
+            return (
+                <div ref={this.getNodeRef} />
+            );
+        }
+
+        // Render prop pattern; pass height to child render function
+        return (
+            <div ref={this.getNodeRef} className={this.props.className} style={{height: this.state.height+'px'}}>
+                { this.props.children({height: this.state.height}) }
+            </div>
+        );
+    }
+};
+
+export default FillHeight;
diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.js
index 8c3746d77c2..8dee19a22df 100644
--- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.js
+++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.js
@@ -21,6 +21,8 @@ import ReactTableContainer from "react-table-container";
 import moment from 'moment';
 import {withRouter} from "react-router";
 import classNames from "classnames";
+import FillHeight from '../components/FillHeight.js';
+
 
 // CSS
 import '../themes/lofar-styles.css';
@@ -243,6 +245,7 @@ class RTSMLines extends Component {
         this.setState({
             displaySingleTests: !this.state.displaySingleTests
         });
+        this.props.callback();
     };
 
     render() {
@@ -310,7 +313,8 @@ class ComponentClass extends Component {
                            component_type={this.props.type}
                            station_name={this.props.station_name}
                            station_type={this.props.station_type}
-                           data={data}/>)
+                           data={data}
+                           callback={this.rerender} />)
     }
 
     renderTestLines(data, component_ids) {
@@ -340,11 +344,17 @@ class ComponentClass extends Component {
         return rows
     }
 
+    rerender = () => {
+        this.setState({ state: this.state });
+    }
+
     render() {
         const comp_ids = this.computeComponentIDList(this.props.type);
+        //<ReactTableContainer width="100%" height="79vh">
+        this.i++;
 
         return (
-            <ReactTableContainer width="100%" height="79vh">
+            <ReactTableContainer width="100%" height={this.props.height+'px'} id={this.i}>
                 <table className="stv-table table-sm table-hover table-bordered">
                     <thead className="stv-tableheader">
                         <tr>
@@ -365,6 +375,21 @@ class ComponentClass extends Component {
     }
 }
 
+/*
+ * Render a Tab item
+ */
+function Tab({label, onClick, isActive}) {
+    const cls = isActive ? 'clickable-tab-active' : 'clickable-tab-unactive';
+
+    return (
+        <NavItem className="clickable-tab">
+            <NavLink className={cls} onClick={onClick}>
+                {label}
+            </NavLink>
+        </NavItem>
+    );
+}
+
 /**
  * StationTestView class.
  */
@@ -374,15 +399,6 @@ class StationTestViewC extends Component {
         activeTab: undefined
     }
 
-    toggleTab = (e) => {
-        let tab = e.currentTarget.innerHTML;
-        if (this.state.activeTab !== tab) {
-            this.setState({
-                activeTab: tab
-            });
-        }
-    }
-
     // Set the activeTab to the first component if it wasn't set yet or when
     // the station changed and doesn't have the active component
     static getDerivedStateFromProps(props, state) {
@@ -401,43 +417,54 @@ class StationTestViewC extends Component {
         return null;
     }
 
-    tabClass = (componentType) => {
-        const className = (this.state.activeTab === componentType ? 'active' : 'unactive');
-        return 'clickable-tab-'+className
-    }
-
     // Do not (re)render when data is loading (performance improvement)
     shouldComponentUpdate(nextProps, nextState, nextContext) {
         return nextProps.isLoading ? false : true;
     }
 
+    toggleTab = (e) => {
+        let tab = e.currentTarget.innerHTML;
+        if (this.state.activeTab !== tab) {
+            this.setState({
+                activeTab: tab
+            });
+        }
+    }
+
     render() {
         const stationType = LOFARDefinitions.stationTypeFromName(this.props.selectedStation);
         const componentTypes = Object.keys(this.props.data).sort();
 
+        if (this.props.isLoading) {
+            return null;
+        }
+
         return (
             <div>
                 <Nav tabs className="component-type-selector">
-                {   componentTypes.map((componentType, key) =>
-                    <NavItem key={key} className="clickable-tab">
-                        <NavLink className={this.tabClass(componentType)}
-                                 onClick={this.toggleTab}>
-                            {componentType}
-                        </NavLink>
-                    </NavItem>)
+                {
+                    componentTypes.map((componentType, key) =>
+                        <Tab key={key} onClick={this.toggleTab} isActive={this.state.activeTab===componentType} label={componentType} />
+                    )
                 }
                 </Nav>
-                <TabContent activeTab={this.state.activeTab}>
-                {   componentTypes.map((componentType, key) =>
-                    <TabPane key={key} tabId={componentType}>
-                        <ComponentClass key={componentType}
-                                        station_type={stationType}
-                                        type={componentType}
-                                        station_name={this.props.selectedStation}
-                                        data={this.props.data[componentType]} />
-                    </TabPane>)
-                }
-                </TabContent>
+                <FillHeight className="border-right">
+                    { ({height}) => (
+                        <TabContent  activeTab={this.state.activeTab}>
+                        {componentTypes.map((componentType, key) =>
+                            <TabPane key={key} tabId={componentType}>
+                                <ComponentClass key={componentType}
+                                                station_type={stationType}
+                                                type={componentType}
+                                                station_name={this.props.selectedStation}
+                                                data={this.props.data[componentType]}
+                                                height={height}
+                                />
+                            </TabPane>
+                        )}
+                        </TabContent>
+                    )}
+                </FillHeight>
             </div>);
     }
 
diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.js
index bd52ef91c6a..8845afbc9f0 100644
--- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.js
+++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.js
@@ -21,6 +21,7 @@ import MultiSelectDropdown from '../components/MultiSelectDropdown.js'
 import {AntennaIdsPerTypeStationType} from '../utils/LOFARDefinitions.js'
 // CSS
 import './Toolbar.css'
+import 'react-datepicker/dist/react-datepicker.css';
 
 
 /**
diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage.js
index 10863e66da0..40df6c6b928 100644
--- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage.js
+++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage.js
@@ -9,47 +9,81 @@ import StationTestView from '../components/StationTestView';
 import StationTestChildView from '../components/StationTestChildView';
 import { composeQueryString } from '../utils/utils.js';
 import { Toolbar, DateRangeSelector, TestTypeSelector, ErrorTypesSelector } from '../components/Toolbar'
+import FillHeight from '../components/FillHeight.js';
 
-// CSS
-import 'react-datepicker/dist/react-datepicker.css';
 
+/*
+ * Display an Alert
+ */
+function ErrorAlert({doDisplay, message}){
+
+    if (! doDisplay) {
+        return null;
+    }
+
+    // The 10px is the margin that ResponsiveGridLayout uses
+    return (
+        <Alert style={{margin: '10px'}} color="warning">
+            {message}
+        </Alert>
+    );
+}
+
+/*
+ * Display the page body (below the header)
+ */
+function PageBody({doDisplay, url}) {
+
+    if (! doDisplay) {
+        return null;
+    }
+
+    return (
+        <Container fluid={true}>
+            <Row>
+                <Col md="8" className="col-padding">
+                    <StationTestView url={url} />
+                </Col>
+                <Col md="4" className="col-padding">
+                    <StationTestChildView />
+                </Col>
+            </Row>
+        </Container>
+    );
+}
 
 
 class StationOverviewPageC extends Component {
 
-    getStationSummaryURL(){
-        const parameters = {};
+    getStationSummaryURL() {
 
         if (this.isParameterMissing()) {
-            return "";
+            return '';
         }
 
-        parameters.station_name = this.props.selectedStation;
-        parameters.test_type = this.props.testType;
-        parameters.from_date = moment(this.props.startDate).format('YYYY-MM-DD');
-        parameters.to_date = moment(this.props.endDate).format('YYYY-MM-DD');
-        parameters.error_types = this.props.selectedErrorTypes
-        const baseURL = '/api/view/ctrl_station_component_errors?format=json';
+        const parameters = {
+            format: 'json',
+            station_name: this.props.selectedStation,
+            test_type: this.props.testType,
+            from_date: moment(this.props.startDate).format('YYYY-MM-DD'),
+            to_date: moment(this.props.endDate).format('YYYY-MM-DD'),
+            error_types: this.props.selectedErrorTypes
+        };
+
+        const baseURL = '/api/view/ctrl_station_component_errors';
         const queryString = composeQueryString(parameters);
-        return `${baseURL}&${queryString}`
+
+        return `${baseURL}?${queryString}`
     }
 
     isParameterMissing(){
         const stationName = this.props.selectedStation;
-        return stationName === undefined ||
-               stationName === ""
-    }
-
-    renderErrorIfParameterMissing(){
-        if (this.isParameterMissing()){
-            // The 10px is the margin that ResponsiveGridLayout uses
-            return <Alert style={{margin: '10px'}} color="warning">Please select a station</Alert>
-        }else {
-            return ""
-        }
+        return stationName === undefined || stationName === "";
     }
 
     render() {
+        const parmMissing = this.isParameterMissing();
+
         return (
             <React.Fragment>
                 <Header active_page={this.props.location} />
@@ -59,15 +93,8 @@ class StationOverviewPageC extends Component {
                     <DateRangeSelector />
                     <ErrorTypesSelector />
                 </Toolbar>
-                {this.renderErrorIfParameterMissing()}
-                { this.isParameterMissing() ? "" :
-                    <Container fluid={true} style={{padding: '10px'}}>
-                        <Row>
-                            <Col md="8"><StationTestView url={this.getStationSummaryURL()}/></Col>
-                            <Col md="4"><StationTestChildView /></Col>
-                        </Row>
-                    </Container>
-                }
+                <ErrorAlert doDisplay={parmMissing} message="Please select a station" />
+                <PageBody doDisplay={!parmMissing} url={this.getStationSummaryURL()} />
             </React.Fragment>
     );
   }
diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage.js
index a25e826d39c..4aa496d0baa 100644
--- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage.js
+++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage.js
@@ -61,8 +61,12 @@ class TilesPageC extends Component {
         { parmMissing ? null :
             <Container fluid={true} style={{padding: '10px'}}>
                 <Row>
-                    <Col md="8"><AntennaView url={url}/></Col>
-                    <Col md="4"><AntennaErrorDetails/></Col>
+                    <Col md="8" className="col-padding">
+                        <AntennaView url={url}/>
+                    </Col>
+                    <Col md="4" className="col-padding">
+                        <AntennaErrorDetails/>
+                    </Col>
                 </Row>
             </Container>
         }
diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.css
index 7bc0991b0af..3a0c2bded0c 100644
--- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.css
+++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.css
@@ -89,3 +89,7 @@ img {
 
 .form-input button:hover {
   background-color: #e8e8ec !important; }
+
+/* Padding on column in bootstrap grid */
+.row .col-padding {
+  padding-top: 15px; }
diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.scss
index 76cbcace81d..a06cfc16b5f 100644
--- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.scss
+++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.scss
@@ -106,3 +106,9 @@ $griditem-color: $secondary-dark;
 .form-input button:hover {
     background-color: $secondary-light !important;
 }
+
+
+/* Padding on column in bootstrap grid */
+.row .col-padding {
+    padding-top: 15px;
+}
diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.css
index 8ef4aa74ceb..aab891291b8 100644
--- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.css
+++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.css
@@ -6408,3 +6408,7 @@ img {
 
 .form-input button:hover {
   background-color: #e8e8ec !important; }
+
+/* Padding on column in bootstrap grid */
+.row .col-padding {
+  padding-top: 15px; }
-- 
GitLab