diff --git a/web/package.json b/web/package.json index 11b3913..c9b27e2 100644 --- a/web/package.json +++ b/web/package.json @@ -28,7 +28,7 @@ "source-map-explorer": "^2.5.3" }, "scripts": { - "start": "export NODE_OPTIONS=--openssl-legacy-provider && react-scripts start", + "start": "export NODE_OPTIONS=--openssl-legacy-provider && WDS_SOCKET_PORT=80 react-scripts start", "build": "export NODE_OPTIONS=--openssl-legacy-provider && react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" diff --git a/web/src/Components/Bridging/Graphs/BridgeGraph.tsx b/web/src/Components/Bridging/Graphs/BridgeGraph.tsx index d2547ee..8952d92 100644 --- a/web/src/Components/Bridging/Graphs/BridgeGraph.tsx +++ b/web/src/Components/Bridging/Graphs/BridgeGraph.tsx @@ -19,8 +19,8 @@ const CustomTooltip = ({ active, payload, label }) => {
{`Particle size : ${label}µm`}
- {payload.map((graphData: any) => ( -
{`${graphData.name}: ${graphData.value}%`}
+ {payload.map((graphData: any, index: number) => ( +
{`${graphData.name}: ${graphData.value}%`}
))}
diff --git a/web/src/Components/Combinations/CombinationCard.tsx b/web/src/Components/Combinations/CombinationCard.tsx index d684716..f4f65e5 100644 --- a/web/src/Components/Combinations/CombinationCard.tsx +++ b/web/src/Components/Combinations/CombinationCard.tsx @@ -1,16 +1,15 @@ import React, { useContext, useEffect, useState } from 'react' -// @ts-ignore -import { Button, Icon, Switch, Input, Tooltip, Divider } from '@equinor/eds-core-react' +import { Button, Icon, Switch, Input, Tooltip } from '@equinor/eds-core-react' import CombinationTable from './CombinationTable' import styled from 'styled-components' import { Card } from './CardContainer' import { Bridge, Combination, GraphData, Product, Products } from '../../Types' import EditProducts from '../Common/EditProducts' import { CombinationAPI } from '../../Api' -import { ErrorToast } from '../Common/Toast' import { findDValue, findGraphData } from '../../Utils' import { IAuthContext, AuthContext } from 'react-oauth2-code-pkce' -import { edit, close, delete_to_trash } from '@equinor/eds-icons' +import { edit, delete_to_trash } from '@equinor/eds-icons' +import { toast } from 'react-toastify' const CardHeader = styled.div` display: flex; @@ -82,6 +81,11 @@ export const CombinationCard = ({ let newMass: number = 0 let newDensity: number = 0 Object.values(combination.values).forEach(prod => { + if (!allProducts[prod.id]) { + console.error(`Product with id '${prod.id}' not found in allProducts`) + toast.error(`Product with id '${prod.id}' not found. Try resetting the application data.`, { autoClose: false }) + return + } newMass += allProducts[prod.id].sackSize * prod.value newDensity += prod.value }) @@ -102,7 +106,7 @@ export const CombinationCard = ({ }) .catch(error => { console.error('fetch error' + error) - ErrorToast(`${error.response.data}`, error.response.status) + toast.error(`Failed to calculate bridge for combination '${combination.name}'`, { autoClose: false }) }) }, [combination, allProducts]) diff --git a/web/src/Components/Common/ErrorBoundary.tsx b/web/src/Components/Common/ErrorBoundary.tsx new file mode 100644 index 0000000..55070d7 --- /dev/null +++ b/web/src/Components/Common/ErrorBoundary.tsx @@ -0,0 +1,57 @@ +import { Icon, List, Typography } from '@equinor/eds-core-react' +import { account_circle } from '@equinor/eds-icons' +import { Component, ErrorInfo, ReactNode } from 'react' + +interface Props { + children?: ReactNode +} + +interface State { + hasError: boolean +} + +class ErrorBoundary extends Component { + constructor(props) { + super(props) + this.state = { hasError: false } + } + // public state: State = { + // hasError: false, + // } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + static getDerivedStateFromError(_: Error): State { + // Update state so the next render will show the fallback UI. + return { hasError: true } + } + + public componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('Uncaught error:', error) + } + + render() { + if (this.state.hasError) { + return ( + <> + Ops... Something went wrong 😞 + You should try the following: + + Refresh the page (F5) + + Resetting the application by clicking the icon, and then{' '} + "reset application data". + + + Contact technical support by email at{' '} + fg_team_hermes@equinor.com + + + + ) + } + + return this.props.children + } +} + +export default ErrorBoundary diff --git a/web/src/Components/Common/Toast.tsx b/web/src/Components/Common/Toast.tsx index 4dac491..70cb1df 100644 --- a/web/src/Components/Common/Toast.tsx +++ b/web/src/Components/Common/Toast.tsx @@ -1,8 +1,6 @@ import { toast } from 'react-toastify' import React from 'react' -export const InfoToast = (msg: string) => toast.info(msg) -export const WarningToast = (msg: string) => toast.warning(msg) export const ErrorToast = (msg: string, code?: number): any => { const title =

Error

const config: Object = { autoClose: 7000 } diff --git a/web/src/Components/Navbar/ContactButton.tsx b/web/src/Components/Navbar/ContactButton.tsx index 8be1671..3108881 100644 --- a/web/src/Components/Navbar/ContactButton.tsx +++ b/web/src/Components/Navbar/ContactButton.tsx @@ -7,11 +7,11 @@ export const ContactButton = () => { const [dialogOpen, setDialogOpen] = useState(false) return ( - <> - +
Contact
+ Contact and support @@ -32,6 +32,6 @@ export const ContactButton = () => { - + ) } diff --git a/web/src/Components/Navbar/CreateProduct.tsx b/web/src/Components/Navbar/CreateProduct.tsx index 28e79ec..d77cb34 100644 --- a/web/src/Components/Navbar/CreateProduct.tsx +++ b/web/src/Components/Navbar/CreateProduct.tsx @@ -1,12 +1,11 @@ import React, { useContext, useState } from 'react' // @ts-ignore -import { Button, Dialog, Icon, TextField, Table } from '@equinor/eds-core-react' +import { Button, Dialog, Icon, Table, Tabs, TextField } from '@equinor/eds-core-react' import { ProductsAPI } from '../../Api' import styled from 'styled-components' import { ErrorToast } from '../Common/Toast' -import { AuthContext } from 'react-oauth2-code-pkce' -import { IAuthContext } from 'react-oauth2-code-pkce' +import { AuthContext, IAuthContext } from 'react-oauth2-code-pkce' import { upload } from '@equinor/eds-icons' import { TNewProduct } from '../../Types' import { toast } from 'react-toastify' @@ -30,7 +29,11 @@ const parseCumulativeProductCurve = (curve: string): number[][] => { return elements.map(element => parseFloat(element)) }) - return parsedData + // Filter out bad input + return parsedData.filter(dataPoint => { + if (dataPoint.length !== 2) return false + return !Number.isNaN(dataPoint[0]) && !Number.isNaN(dataPoint[1]) + }) } export const RefreshButton = () => { @@ -39,6 +42,7 @@ export const RefreshButton = () => { const { token }: IAuthContext = useContext(AuthContext) const [newProduct, setNewProduct] = useState() const [newProductData, setNewProductData] = useState([]) + const [activeTab, setActiveTab] = useState(0) const postProduct = () => { ProductsAPI.postProductsApi(token, { ...newProduct, productData: newProductData }) @@ -56,93 +60,113 @@ export const RefreshButton = () => { } return ( - <> - +
Create product
+ Define a new product -
- setNewProduct({ ...newProduct, productName: event.target.value })} - /> - setNewProduct({ ...newProduct, productSupplier: event.target.value })} - /> -
-
-

- Paste the product's measured data values here. Make sure it's been parsed correctly by inspecting the - table below before submitting. -

-

- The format of the pasted data should be two numbers on each line (space or tab separated), where the first - number is the fraction size in micron of the measuring point, and the other the cumulative volume - percentage. -

-

- The Optimizer requires each product to have 100 data points, from 0.01 - 3500 microns. If the data you - provide is missing data, the values will be interpolated and extrapolated. -

- setNewProductData(parseCumulativeProductCurve(event.target.value))} - /> -
-
- - - - Index - Fraction(micron) - Cumulative - - - {newProductData.map((dataPoint: any, index) => ( - - {index} - {dataPoint[0]} - {dataPoint[1]} - - ))} -
-
+ setActiveTab(e)}> + + Details + Verify + + + +
+ setNewProduct({ ...newProduct, productName: event.target.value })} + /> + setNewProduct({ ...newProduct, productSupplier: event.target.value })} + /> +
+
+

Paste the product's measured data values here.

+

+ The format of the pasted data should be two numbers on each line (space or tab separated), where the + first number is the fraction size in micron of the measuring point, and the other the cumulative + volume percentage. +

+

+ The Optimizer requires each product to have 100 data points, from 0.01 - 3500 microns. If the data + you provide is missing data, the values will be interpolated and extrapolated. +

+ setNewProductData(parseCumulativeProductCurve(event.target.value))} + /> +
+
+ +

Verify that the data looks correct before submitting. Go back and correct the input if necessary.

+
+ + + + Index + Fraction(micron) + Cumulative(%) + + + {newProductData.map((dataPoint: any, index) => ( + + {index} + {dataPoint[0]} + {dataPoint[1]} + + ))} +
+
+
+
+
- - + {activeTab === 0 ? ( + + ) : ( + + )} + {activeTab === 0 ? ( + + ) : ( + + )}
- + ) } diff --git a/web/src/Components/Navbar/Navbar.tsx b/web/src/Components/Navbar/Navbar.tsx index b1ea764..50688e9 100644 --- a/web/src/Components/Navbar/Navbar.tsx +++ b/web/src/Components/Navbar/Navbar.tsx @@ -1,42 +1,55 @@ -import { Button, Icon, TopBar, Typography } from '@equinor/eds-core-react' +import { Button, Icon, Menu, TopBar, Typography } from '@equinor/eds-core-react' import RefreshButton from './RefreshButton' import { ContactButton } from './ContactButton' -import { info_circle } from '@equinor/eds-icons' +import { external_link, menu } from '@equinor/eds-icons' import { StyledLink } from './styles' import CreateProduct from './CreateProduct' +import { useState } from 'react' +import ResetApp from './ResetAppData' +import ResetAppData from './ResetAppData' const Navbar = () => { + const [isMenuOpen, setIsMenuOpen] = useState(false) + const [anchorEl, setAnchorEl] = useState(null) return ( LCM Optimizer -
setIsMenuOpen(!isMenuOpen)} ref={setAnchorEl}> + + + setIsMenuOpen(false)} + anchorEl={anchorEl} > -
+ + - + LCM Library SharePoint -
- - -
+ + -
-
+ + + + + + + + + + +
) diff --git a/web/src/Components/Navbar/RefreshButton.tsx b/web/src/Components/Navbar/RefreshButton.tsx index a6886b0..78d406e 100644 --- a/web/src/Components/Navbar/RefreshButton.tsx +++ b/web/src/Components/Navbar/RefreshButton.tsx @@ -1,6 +1,6 @@ import React, { useContext, useState } from 'react' // @ts-ignore -import { Button, Dialog, CircularProgress, Icon } from '@equinor/eds-core-react' +import { Button, Dialog, CircularProgress, Icon, Typography } from '@equinor/eds-core-react' import { SyncAPI } from '../../Api' import styled from 'styled-components' @@ -34,11 +34,11 @@ export const RefreshButton = () => { } return ( - <> - +
Synchronize with SharePoint
+ Synchronize SharePoint data @@ -74,7 +74,7 @@ export const RefreshButton = () => { - + ) } diff --git a/web/src/Components/Navbar/ResetAppData.tsx b/web/src/Components/Navbar/ResetAppData.tsx new file mode 100644 index 0000000..3d01bf4 --- /dev/null +++ b/web/src/Components/Navbar/ResetAppData.tsx @@ -0,0 +1,55 @@ +import React, { useState } from 'react' +// @ts-ignore +import { Button, Dialog, Icon } from '@equinor/eds-core-react' + +import { sync_problem } from '@equinor/eds-icons' +import styled from 'styled-components' + +const ButtonWrapper = styled.div` + display: flex; + justify-content: space-between; + width: 100%; +` + +export const ResetApp = () => { + const [dialogOpen, setDialogOpen] = useState(false) + + return ( +
+
setDialogOpen(true)} style={{ display: 'flex', alignItems: 'center' }}> + +
Reset application
+
+ + + Reset application + + +
+ Are you sure you want to reset the application? This will remove all stored combinations and blends. +
+
+ + + + + + +
+
+ ) +} + +export default ResetApp diff --git a/web/src/Components/Navbar/styles.ts b/web/src/Components/Navbar/styles.ts index d1721f7..ffafc28 100644 --- a/web/src/Components/Navbar/styles.ts +++ b/web/src/Components/Navbar/styles.ts @@ -2,7 +2,5 @@ import { styled } from 'styled-components' export const StyledLink = styled.a` color: #007079; - font-size: 16px; - line-height: 20px; text-decoration-line: underline; ` diff --git a/web/src/Pages/Main.tsx b/web/src/Pages/Main.tsx index 077883a..090dad3 100644 --- a/web/src/Pages/Main.tsx +++ b/web/src/Pages/Main.tsx @@ -10,6 +10,7 @@ import { ErrorToast } from '../Components/Common/Toast' import { AuthContext } from 'react-oauth2-code-pkce' import { IAuthContext } from 'react-oauth2-code-pkce' import Navbar from '../Components/Navbar/Navbar' +import ErrorBoundary from '../Components/Common/ErrorBoundary' const BodyWrapper = styled.div` display: flex; @@ -37,11 +38,13 @@ export default (): ReactElement => { return ( <> - - - {/* @ts-ignore*/} - - + + + + {/* @ts-ignore*/} + + + ) }