Merge branch 'staging_environment' into 3d_bin
1
.env
|
|
@ -14,6 +14,7 @@ VITE_AUTH0_ADAPTIVE_CONSTRUCTION_CLIENT_ID=32rABabJzXRvJiWivTmeKFgwFiqh4ok7
|
||||||
VITE_AUTH0_AEROGROW_CLIENT_ID=KHl9ooUt1nia1RYw5n224dyggCXdbsSd
|
VITE_AUTH0_AEROGROW_CLIENT_ID=KHl9ooUt1nia1RYw5n224dyggCXdbsSd
|
||||||
VITE_AUTH0_MIVENT_CLIENT_ID=VNALE7RW6l3dY5uYcxgwElZV0lcT25Fg
|
VITE_AUTH0_MIVENT_CLIENT_ID=VNALE7RW6l3dY5uYcxgwElZV0lcT25Fg
|
||||||
VITE_AUTH0_OMNIAIR_CLIENT_ID=IblmarD8wFafiD6doxTmOHQ6Bx3L9wWl
|
VITE_AUTH0_OMNIAIR_CLIENT_ID=IblmarD8wFafiD6doxTmOHQ6Bx3L9wWl
|
||||||
|
VITE_AUTH0_INTELLIFARMS_CLIENT_ID=RYtuAyOcB4DSaaqJMLDMf3pV8SFY9PdY
|
||||||
|
|
||||||
#Branding (Default theme)
|
#Branding (Default theme)
|
||||||
VITE_APP_WEBSITE_TITLE="Adaptive Dashboard"
|
VITE_APP_WEBSITE_TITLE="Adaptive Dashboard"
|
||||||
|
|
|
||||||
14
README.md
|
|
@ -18,7 +18,10 @@ Steps to add a new white label
|
||||||
- add a `CNAME` record pointing their custom subdomain (ex: dashboard.example.com) to brandxtech.ca
|
- add a `CNAME` record pointing their custom subdomain (ex: dashboard.example.com) to brandxtech.ca
|
||||||
- add a login button on their website point to `/login` of their custom subdomain (ex: dashboard.example.com/login)
|
- add a login button on their website point to `/login` of their custom subdomain (ex: dashboard.example.com/login)
|
||||||
|
|
||||||
|
It is recommended to start with the Auth0 stage once everything from the client has been recieved as things like the client ID will be needed in other stages
|
||||||
|
|
||||||
<!-- frontend steps -->
|
<!-- frontend steps -->
|
||||||
|
Frontend Steps
|
||||||
1. Create a PWA folder in `public` that is named after the client and generate PWA assets using [this](https://realfavicongenerator.net/) website (use the company logo but you might need to use a custom favicon depending on the result)
|
1. Create a PWA folder in `public` that is named after the client and generate PWA assets using [this](https://realfavicongenerator.net/) website (use the company logo but you might need to use a custom favicon depending on the result)
|
||||||
|
|
||||||
- https://maskable.app/ also create a maskable icon for PWA
|
- https://maskable.app/ also create a maskable icon for PWA
|
||||||
|
|
@ -29,22 +32,23 @@ Steps to add a new white label
|
||||||
4. If there are any pages specific to the whitelabel be sure to update the side and bottom navigator
|
4. If there are any pages specific to the whitelabel be sure to update the side and bottom navigator
|
||||||
|
|
||||||
<!-- backend steps -->
|
<!-- backend steps -->
|
||||||
|
Backend Steps
|
||||||
1. In `dynamic-config.prod.yml`, add a router entry for the new whitelabel
|
1. In `dynamic-config.prod.yml`, add a router entry for the new whitelabel
|
||||||
2. In `pond/http/server.go`, add the sub-domain to the `allowedOrigins`
|
2. In `pond/http/server.go`, add the sub-domain to the `allowedOrigins`
|
||||||
3. In `pond/whitelabel.go`, add an entry for the new white label
|
3. In `pond/whitelabel.go`, add an entry for the new white label
|
||||||
4. Enable the white-label sub-domain for CORS in the backend by adding it to the allowed origins http/server.go
|
|
||||||
|
|
||||||
<!-- Auth steps -->
|
<!-- Auth steps -->
|
||||||
1. Create a new `Auth0` application for the client
|
Auth0 Steps
|
||||||
|
1. Create a new Single Page App on Auth0 using React as the tech
|
||||||
|
2. Once you have the client ID add it to your .env file in the frontend
|
||||||
|
3. Adjust settings accordingly in the new application
|
||||||
- use a image bucket like [postimg](https://postimages.org/) to store their company logo and favicon
|
- use a image bucket like [postimg](https://postimages.org/) to store their company logo and favicon
|
||||||
- add a whitelabel logo
|
- add a whitelabel logo
|
||||||
- allow necessary `origins`, `callback URLs`, and `CORS domains` to the application settings (see the default application as a guideline)
|
- allow necessary `origins`, `callback URLs`, and `CORS domains` to the application settings (see other applications/whitelabels as a guideline)
|
||||||
- customize the Universal Login page to handle the new whitelabel
|
- customize the Universal Login page to handle the new whitelabel
|
||||||
- check if the client ID matches this client's Auth0 application
|
- check if the client ID matches this client's Auth0 application
|
||||||
- change the Universal Login theme using white-label colours and images stored in a bucket (ex: [postimg](https://postimages.org/))
|
- change the Universal Login theme using white-label colours and images stored in a bucket (ex: [postimg](https://postimages.org/))
|
||||||
|
|
||||||
- in [http.go](https://gitlab.com/brandx/backend/blob/master/pond/http.go), add the white-label sub-domain to the `AllowedOrigins` list
|
|
||||||
|
|
||||||
## Expanding the ESLint configuration
|
## Expanding the ESLint configuration
|
||||||
|
|
||||||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||||
|
|
|
||||||
BIN
public/Intellifarms/IFND-2023-Logo-White.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
public/Intellifarms/IFND-2023-Logo.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
public/Intellifarms/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/Intellifarms/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
public/Intellifarms/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
9
public/Intellifarms/browserconfig.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/mstile-150x150.png"/>
|
||||||
|
<TileColor>#ffffff</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
||||||
BIN
public/Intellifarms/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 712 B |
BIN
public/Intellifarms/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
public/Intellifarms/favicon-48x48.png
Normal file
|
After Width: | Height: | Size: 3 KiB |
BIN
public/Intellifarms/favicon.ico
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
19
public/Intellifarms/manifest.json
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "Intellifarms",
|
||||||
|
"short_name": "Intellifarms",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
BIN
public/Intellifarms/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
3
public/Intellifarms/safari-pinned-tab.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||||
|
<image href="Intellifarms_Icon.png" height="512" width="512"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 134 B |
BIN
src/assets/whitelabels/Intellifarms/IFND-2023-Logo-White.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
src/assets/whitelabels/Intellifarms/IFND-2023-Logo.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
|
|
@ -57,6 +57,8 @@ interface Props {
|
||||||
components?: Map<string, Component>;
|
components?: Map<string, Component>;
|
||||||
setComponents?: React.Dispatch<React.SetStateAction<Map<string, Component>>>;
|
setComponents?: React.Dispatch<React.SetStateAction<Map<string, Component>>>;
|
||||||
updateBinStatus?: (componentKeys: string[], removed?: boolean) => void;
|
updateBinStatus?: (componentKeys: string[], removed?: boolean) => void;
|
||||||
|
componentDevices?: Map<string, number>
|
||||||
|
setComponentDevices?: React.Dispatch<React.SetStateAction<Map<string, number>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OpenState {
|
interface OpenState {
|
||||||
|
|
@ -77,7 +79,9 @@ export default function BinActions(props: Props) {
|
||||||
refreshCallback,
|
refreshCallback,
|
||||||
userID,
|
userID,
|
||||||
components,
|
components,
|
||||||
|
componentDevices,
|
||||||
setComponents,
|
setComponents,
|
||||||
|
setComponentDevices,
|
||||||
updateBinStatus
|
updateBinStatus
|
||||||
} = props;
|
} = props;
|
||||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
|
|
@ -245,7 +249,9 @@ export default function BinActions(props: Props) {
|
||||||
open={openState.sensors}
|
open={openState.sensors}
|
||||||
userID={userID}
|
userID={userID}
|
||||||
components={components}
|
components={components}
|
||||||
|
componentDevices={componentDevices}
|
||||||
setComponents={setComponents}
|
setComponents={setComponents}
|
||||||
|
setComponentDevices={setComponentDevices}
|
||||||
updateBinStatus={updateBinStatus}
|
updateBinStatus={updateBinStatus}
|
||||||
onClose={refresh => {
|
onClose={refresh => {
|
||||||
if (refresh === true) {
|
if (refresh === true) {
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,9 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
components?: Map<string, Component>;
|
components?: Map<string, Component>;
|
||||||
|
componentDevices?: Map<string, number>;
|
||||||
setComponents?: React.Dispatch<React.SetStateAction<Map<string, Component>>>;
|
setComponents?: React.Dispatch<React.SetStateAction<Map<string, Component>>>;
|
||||||
|
setComponentDevices?: React.Dispatch<React.SetStateAction<Map<string, number>>>;
|
||||||
bin: string;
|
bin: string;
|
||||||
binGrain: pond.Grain;
|
binGrain: pond.Grain;
|
||||||
updateBinStatus?: (componentKeys: string[], removed?: boolean) => void;
|
updateBinStatus?: (componentKeys: string[], removed?: boolean) => void;
|
||||||
|
|
@ -84,7 +86,7 @@ interface Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BinComponents(props: Props) {
|
export default function BinComponents(props: Props) {
|
||||||
const { components, bin, setComponents, updateBinStatus, binGrain } = props;
|
const { components, componentDevices, bin, setComponents, setComponentDevices, updateBinStatus, binGrain } = props;
|
||||||
const [{as}] = useGlobalState();
|
const [{as}] = useGlobalState();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const binAPI = useBinAPI();
|
const binAPI = useBinAPI();
|
||||||
|
|
@ -208,20 +210,54 @@ export default function BinComponents(props: Props) {
|
||||||
// }
|
// }
|
||||||
// }, [selectedDevice, componentAPI, deviceComponents, snackbar]);
|
// }, [selectedDevice, componentAPI, deviceComponents, snackbar]);
|
||||||
|
|
||||||
const removeComponent = (component: string) => {
|
const removeComponent = (component: string, device?: number) => {
|
||||||
binAPI.removeComponent(bin, component, as).then(() => {
|
console.log(device)
|
||||||
|
binAPI.removeComponent(bin, component, device, as).then(() => {
|
||||||
|
|
||||||
if (components && setComponents) {
|
if (components && setComponents) {
|
||||||
if (components.delete(component)) {
|
if (components.delete(component)) {
|
||||||
let newComponents = new Map(components);
|
let newComponents = new Map(components);
|
||||||
setComponents(newComponents);
|
setComponents(newComponents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(componentDevices && setComponentDevices) {
|
||||||
|
if (componentDevices.delete(component)) {
|
||||||
|
let newComponentDevices = new Map(componentDevices);
|
||||||
|
setComponentDevices(newComponentDevices);
|
||||||
|
}
|
||||||
|
}
|
||||||
snackbar.info("Component removed from bin");
|
snackbar.info("Component removed from bin");
|
||||||
});
|
});
|
||||||
setComponentToRemove(undefined);
|
setComponentToRemove(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeComponentConfirmation = () => {
|
const removeComponentConfirmation = (last?: boolean) => {
|
||||||
|
let lastComponent = false;
|
||||||
|
let devId: number | undefined;
|
||||||
|
if(last){
|
||||||
|
lastComponent = last
|
||||||
|
}else{
|
||||||
|
if (componentDevices && componentToRemove) {
|
||||||
|
const id = componentDevices.get(componentToRemove.key());
|
||||||
|
|
||||||
|
if (id !== undefined) {
|
||||||
|
devId = id
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
for (const val of componentDevices.values()) {
|
||||||
|
if (val === devId) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (count > 1) {
|
||||||
|
break; // stop early once we know it's not the last
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastComponent = count === 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<ResponsiveDialog
|
<ResponsiveDialog
|
||||||
fullScreen={false}
|
fullScreen={false}
|
||||||
|
|
@ -229,19 +265,37 @@ export default function BinComponents(props: Props) {
|
||||||
onClose={() => setComponentToRemove(undefined)}>
|
onClose={() => setComponentToRemove(undefined)}>
|
||||||
<DialogTitle>Remove {componentToRemove?.name()}?</DialogTitle>
|
<DialogTitle>Remove {componentToRemove?.name()}?</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>
|
{lastComponent ?
|
||||||
This will remove {componentToRemove?.name()} from this bin. If you don't have direct
|
<DialogContentText>
|
||||||
access to this component or the device it is attached to, you will not be able to add it
|
This component {componentToRemove?.name()} is the last component from its device. You can choose to remove only the component itself or the device along with it.
|
||||||
back.
|
Leaving the device may have unexpected results if the device will continue to be used and it is recommended to remove it if the device is being moved to a new bin.
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
|
:
|
||||||
|
<DialogContentText>
|
||||||
|
This will remove {componentToRemove?.name()} from this bin. If you don't have direct
|
||||||
|
access to this component or the device it is attached to, you will not be able to add it
|
||||||
|
back.
|
||||||
|
</DialogContentText>
|
||||||
|
}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setComponentToRemove(undefined)} color="primary">
|
<Button onClick={() => setComponentToRemove(undefined)} color="primary">
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
{lastComponent
|
||||||
|
?
|
||||||
|
<React.Fragment>
|
||||||
|
<Button onClick={() => removeComponent(componentToRemove!.key())} color="primary">
|
||||||
|
Remove Component
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => removeComponent(componentToRemove!.key(), devId)} color="primary">
|
||||||
|
Remove Component And Device
|
||||||
|
</Button>
|
||||||
|
</React.Fragment> :
|
||||||
<Button onClick={() => removeComponent(componentToRemove!.key())} color="primary">
|
<Button onClick={() => removeComponent(componentToRemove!.key())} color="primary">
|
||||||
Remove
|
Remove
|
||||||
</Button>
|
</Button>
|
||||||
|
}
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</ResponsiveDialog>
|
</ResponsiveDialog>
|
||||||
);
|
);
|
||||||
|
|
@ -256,7 +310,7 @@ export default function BinComponents(props: Props) {
|
||||||
<DialogTitle>Remove All Components?</DialogTitle>
|
<DialogTitle>Remove All Components?</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
This will remove All attached components from this bin. If you don't have direct access
|
This will remove All attached components and devices from this bin. If you don't have direct access
|
||||||
to these components or the devices they are attached to, you will not be able to add
|
to these components or the devices they are attached to, you will not be able to add
|
||||||
them back.
|
them back.
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
|
|
@ -320,6 +374,12 @@ export default function BinComponents(props: Props) {
|
||||||
setComponents(newComponents);
|
setComponents(newComponents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(componentDevices && setComponentDevices) {
|
||||||
|
if (componentDevices.set(component.key(), device)) {
|
||||||
|
let newComponentDevices = new Map(componentDevices);
|
||||||
|
setComponentDevices(newComponentDevices);
|
||||||
|
}
|
||||||
|
}
|
||||||
//if a grain cable was added to the bin update the components grain type to match the bin
|
//if a grain cable was added to the bin update the components grain type to match the bin
|
||||||
if (preferences.type === pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE) {
|
if (preferences.type === pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE) {
|
||||||
let settings = component.settings;
|
let settings = component.settings;
|
||||||
|
|
@ -338,15 +398,40 @@ export default function BinComponents(props: Props) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
binAPI.removeComponent(bin, component.key(), as).then(resp => {
|
let lastComponent = false;
|
||||||
if (components && setComponents) {
|
if (componentDevices) {
|
||||||
|
console.log(componentDevices)
|
||||||
|
let count = 0;
|
||||||
|
for (const val of componentDevices.values()) {
|
||||||
|
if (val === device) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (count > 1) {
|
||||||
|
break; // stop early once we know it's not the last
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastComponent = count === 1;
|
||||||
|
}
|
||||||
|
if(lastComponent){
|
||||||
|
setComponentToRemove(component)
|
||||||
|
}else{
|
||||||
|
binAPI.removeComponent(bin, component.key(), undefined, as).then(resp => {
|
||||||
|
if (components && setComponents) {
|
||||||
if (components.delete(component.key())) {
|
if (components.delete(component.key())) {
|
||||||
let newComponents = new Map(components);
|
let newComponents = new Map(components);
|
||||||
setComponents(newComponents);
|
setComponents(newComponents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(componentDevices && setComponentDevices) {
|
||||||
|
if (componentDevices.delete(component.key())) {
|
||||||
|
let newComponentDevices = new Map(componentDevices);
|
||||||
|
setComponentDevices(newComponentDevices);
|
||||||
|
}
|
||||||
|
}
|
||||||
snackbar.info("Component removed from bin");
|
snackbar.info("Component removed from bin");
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -567,7 +652,10 @@ export default function BinComponents(props: Props) {
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText inset={cIcon === undefined}>{component.name()}</ListItemText>
|
<ListItemText inset={cIcon === undefined}>{component.name()}</ListItemText>
|
||||||
<ListItemSecondaryAction>
|
<ListItemSecondaryAction>
|
||||||
<IconButton onClick={() => setComponentToRemove(component)}>
|
<IconButton onClick={() => {
|
||||||
|
setComponentToRemove(component)
|
||||||
|
|
||||||
|
}}>
|
||||||
<Remove />
|
<Remove />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</ListItemSecondaryAction>
|
</ListItemSecondaryAction>
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,9 @@ interface Props {
|
||||||
coords?: { longitude: number; latitude: number };
|
coords?: { longitude: number; latitude: number };
|
||||||
binYards?: pond.BinYardSettings[];
|
binYards?: pond.BinYardSettings[];
|
||||||
components?: Map<string, Component>;
|
components?: Map<string, Component>;
|
||||||
|
componentDevices?: Map<string, number>
|
||||||
setComponents?: React.Dispatch<React.SetStateAction<Map<string, Component>>>;
|
setComponents?: React.Dispatch<React.SetStateAction<Map<string, Component>>>;
|
||||||
|
setComponentDevices?: React.Dispatch<React.SetStateAction<Map<string, number>>>;
|
||||||
updateBinStatus?: (componentKeys: string[], removed?: boolean) => void;
|
updateBinStatus?: (componentKeys: string[], removed?: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,7 +45,9 @@ export default function BinSensors(props: Props) {
|
||||||
mode,
|
mode,
|
||||||
openedBinYard,
|
openedBinYard,
|
||||||
components,
|
components,
|
||||||
|
componentDevices,
|
||||||
setComponents,
|
setComponents,
|
||||||
|
setComponentDevices,
|
||||||
updateBinStatus
|
updateBinStatus
|
||||||
} = props;
|
} = props;
|
||||||
const [initialized, setInitialized] = useState(false);
|
const [initialized, setInitialized] = useState(false);
|
||||||
|
|
@ -93,7 +97,9 @@ export default function BinSensors(props: Props) {
|
||||||
<Box padding={1}>
|
<Box padding={1}>
|
||||||
<BinComponents
|
<BinComponents
|
||||||
components={components ? components : undefined}
|
components={components ? components : undefined}
|
||||||
|
componentDevices={componentDevices}
|
||||||
setComponents={setComponents}
|
setComponents={setComponents}
|
||||||
|
setComponentDevices={setComponentDevices}
|
||||||
bin={bin.key()}
|
bin={bin.key()}
|
||||||
binGrain={bin.grain()}
|
binGrain={bin.grain()}
|
||||||
updateBinStatus={updateBinStatus}
|
updateBinStatus={updateBinStatus}
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,12 @@ interface Props {
|
||||||
multiGraphZoom?: (domain: number[] | string[]) => void;
|
multiGraphZoom?: (domain: number[] | string[]) => void;
|
||||||
colourAboveZero?: ColourData
|
colourAboveZero?: ColourData
|
||||||
colourBelowZero?: ColourData
|
colourBelowZero?: ColourData
|
||||||
|
minY?: string | number
|
||||||
|
maxY?: string | number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SingleSetAreaChart(props: Props) {
|
export default function SingleSetAreaChart(props: Props) {
|
||||||
const { data, maxRef, minRef, newXDomain, multiGraphZoom, yAxisLabel, colourAboveZero, colourBelowZero, tooltipLabel, tooltipUnit } = props;
|
const { data, maxRef, minRef, newXDomain, multiGraphZoom, yAxisLabel, colourAboveZero, colourBelowZero, tooltipLabel, tooltipUnit, minY, maxY } = props;
|
||||||
const [xDomain, setXDomain] = useState<string[] | number[]>(["dataMin", "dataMax"]);
|
const [xDomain, setXDomain] = useState<string[] | number[]>(["dataMin", "dataMax"]);
|
||||||
const [refLeft, setRefLeft] = useState<number | undefined>();
|
const [refLeft, setRefLeft] = useState<number | undefined>();
|
||||||
const [refRight, setRefRight] = useState<number | undefined>();
|
const [refRight, setRefRight] = useState<number | undefined>();
|
||||||
|
|
@ -152,7 +154,8 @@ export default function SingleSetAreaChart(props: Props) {
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
type="number"
|
type="number"
|
||||||
domain={["auto", "auto"]}
|
allowDataOverflow
|
||||||
|
domain={[minY ?? "auto", maxY ?? "auto"]}
|
||||||
label={{
|
label={{
|
||||||
value: yAxisLabel,
|
value: yAxisLabel,
|
||||||
position: "insideLeft",
|
position: "insideLeft",
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,9 @@ export default function GateDeltaTempGraph(props: Props){
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<SingleSetAreaChart
|
<SingleSetAreaChart
|
||||||
data={data}
|
data={data}
|
||||||
|
minY={-40} //-40C is the same as -40F
|
||||||
|
maxY={(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? 104 : 40)}
|
||||||
tooltipLabel="Delta Temp"
|
tooltipLabel="Delta Temp"
|
||||||
tooltipUnit={user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C"}
|
tooltipUnit={user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C"}
|
||||||
yAxisLabel={"Delta T" + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")}
|
yAxisLabel={"Delta T" + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import BinsIcon from "products/Bindapt/BinsIcon";
|
||||||
import { useGlobalState } from "providers";
|
import { useGlobalState } from "providers";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useNavigate, useLocation } from "react-router-dom";
|
import { useNavigate, useLocation } from "react-router-dom";
|
||||||
import { IsAdaptiveAgriculture, isBXT, IsMiVent, IsAdCon, IsOmniAir, IsMiPCA } from "services/whiteLabel";
|
import { IsAdaptiveAgriculture, isBXT, IsMiVent, IsAdCon, IsOmniAir, IsMiPCA, IsIntellifarms } from "services/whiteLabel";
|
||||||
import FieldsIcon from "products/AgIcons/FieldsIcon";
|
import FieldsIcon from "products/AgIcons/FieldsIcon";
|
||||||
import NexusSTIcon from "products/Construction/NexusSTIcon";
|
import NexusSTIcon from "products/Construction/NexusSTIcon";
|
||||||
import OmniAirDeviceIcon from "products/AviationIcons/OmniAirDeviceIcon";
|
import OmniAirDeviceIcon from "products/AviationIcons/OmniAirDeviceIcon";
|
||||||
|
|
@ -36,7 +36,8 @@ export default function BottomNavigator(props: Props) {
|
||||||
const { isAuthenticated } = useAuth0();
|
const { isAuthenticated } = useAuth0();
|
||||||
const [{ user }] = useGlobalState();
|
const [{ user }] = useGlobalState();
|
||||||
const [route, setRoute] = useState(sideIsOpen ? "side" : "");
|
const [route, setRoute] = useState(sideIsOpen ? "side" : "");
|
||||||
const isAdaptive = IsAdaptiveAgriculture();
|
const isAg = IsAdaptiveAgriculture();
|
||||||
|
const isIntel = IsIntellifarms();
|
||||||
const isMiVent = IsMiVent();
|
const isMiVent = IsMiVent();
|
||||||
const isAdCon = IsAdCon();
|
const isAdCon = IsAdCon();
|
||||||
const isOmni = IsOmniAir();
|
const isOmni = IsOmniAir();
|
||||||
|
|
@ -45,7 +46,7 @@ export default function BottomNavigator(props: Props) {
|
||||||
const reRoute = useCallback(
|
const reRoute = useCallback(
|
||||||
(path: string) => {
|
(path: string) => {
|
||||||
if (path === "") {
|
if (path === "") {
|
||||||
if (isAdaptive) {
|
if (isAg || isIntel) {
|
||||||
return "bins";
|
return "bins";
|
||||||
}
|
}
|
||||||
if (isMiVent) {
|
if (isMiVent) {
|
||||||
|
|
@ -74,7 +75,7 @@ export default function BottomNavigator(props: Props) {
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
},
|
},
|
||||||
[isAdaptive, isMiVent]
|
[isAg, isMiVent, isIntel]
|
||||||
);
|
);
|
||||||
|
|
||||||
const autoDetectRoute = useCallback(() => {
|
const autoDetectRoute = useCallback(() => {
|
||||||
|
|
@ -105,10 +106,10 @@ export default function BottomNavigator(props: Props) {
|
||||||
const authenticatedNavigation = () => {
|
const authenticatedNavigation = () => {
|
||||||
return (
|
return (
|
||||||
<BottomNavigation value={route} onChange={(_, newValue) => handleRouteChange(newValue)}>
|
<BottomNavigation value={route} onChange={(_, newValue) => handleRouteChange(newValue)}>
|
||||||
{isAdaptive && (
|
{isAg || isIntel && (
|
||||||
<BottomNavigationAction label="Farm" icon={<FieldsIcon type={getType()} />} value="visualFarm" />
|
<BottomNavigationAction label="Farm" icon={<FieldsIcon type={getType()} />} value="visualFarm" />
|
||||||
)}
|
)}
|
||||||
{isAdaptive && (
|
{isAg || isIntel && (
|
||||||
<BottomNavigationAction label="Bins" icon={<BinsIcon type={getType()} />} value="bins" />
|
<BottomNavigationAction label="Bins" icon={<BinsIcon type={getType()} />} value="bins" />
|
||||||
)}
|
)}
|
||||||
{isAdCon && (
|
{isAdCon && (
|
||||||
|
|
@ -136,7 +137,7 @@ export default function BottomNavigator(props: Props) {
|
||||||
<BottomNavigationAction
|
<BottomNavigationAction
|
||||||
label="Devices"
|
label="Devices"
|
||||||
icon={
|
icon={
|
||||||
isAdaptive ? (
|
(isAg || isIntel) ? (
|
||||||
<BindaptIcon type={getType()} />
|
<BindaptIcon type={getType()} />
|
||||||
) : isAdCon ? (
|
) : isAdCon ? (
|
||||||
<NexusSTIcon type={getType()} />
|
<NexusSTIcon type={getType()} />
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import {
|
||||||
IsAdaptiveAgriculture,
|
IsAdaptiveAgriculture,
|
||||||
// hasTutorialPlaylist,
|
// hasTutorialPlaylist,
|
||||||
IsAdCon,
|
IsAdCon,
|
||||||
|
IsIntellifarms,
|
||||||
IsMiPCA,
|
IsMiPCA,
|
||||||
// isBXT,
|
// isBXT,
|
||||||
IsMiVent,
|
IsMiVent,
|
||||||
|
|
@ -165,14 +166,15 @@ export default function SideNavigator(props: Props) {
|
||||||
|
|
||||||
const authenticatedSideMenu = () => {
|
const authenticatedSideMenu = () => {
|
||||||
const isMiVent = IsMiVent();
|
const isMiVent = IsMiVent();
|
||||||
const isAg = IsAdaptiveAgriculture()
|
const isAg = IsAdaptiveAgriculture()
|
||||||
|
const isIntel = IsIntellifarms()
|
||||||
const isStreamline = IsStreamline()
|
const isStreamline = IsStreamline()
|
||||||
const isOmni = IsOmniAir()
|
const isOmni = IsOmniAir()
|
||||||
const isMiPCA = IsMiPCA()
|
const isMiPCA = IsMiPCA()
|
||||||
const isAdCon = IsAdCon()
|
const isAdCon = IsAdCon()
|
||||||
return (
|
return (
|
||||||
<List className={classes.list} component="nav">
|
<List className={classes.list} component="nav">
|
||||||
{(isAg || isStreamline || user.hasFeature("admin")) && (
|
{(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
|
||||||
<Tooltip title="Visual Farm" placement="right">
|
<Tooltip title="Visual Farm" placement="right">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
id="tour-visual-farm"
|
id="tour-visual-farm"
|
||||||
|
|
@ -214,7 +216,7 @@ export default function SideNavigator(props: Props) {
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{(isAg || isStreamline || user.hasFeature("admin")) && (
|
{(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
|
||||||
<Tooltip title="Contracts" placement="right">
|
<Tooltip title="Contracts" placement="right">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
id="tour-contracts"
|
id="tour-contracts"
|
||||||
|
|
@ -228,7 +230,7 @@ export default function SideNavigator(props: Props) {
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{(isAg || isStreamline || user.hasFeature("admin")) && (
|
{(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
|
||||||
<Tooltip title="Bins" placement="right">
|
<Tooltip title="Bins" placement="right">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
id="tour-bins"
|
id="tour-bins"
|
||||||
|
|
@ -270,7 +272,7 @@ export default function SideNavigator(props: Props) {
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
{(isAg || isStreamline || user.hasFeature("admin")) && (
|
{(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
|
||||||
<Tooltip title="Transactions" placement="right">
|
<Tooltip title="Transactions" placement="right">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
id="tour-transactions"
|
id="tour-transactions"
|
||||||
|
|
@ -362,7 +364,7 @@ export default function SideNavigator(props: Props) {
|
||||||
{open && <ListItemText primary="Users" />}
|
{open && <ListItemText primary="Users" />}
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{(isAg || isStreamline || user.hasFeature("admin")) && (
|
{(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
|
||||||
<Tooltip title="My Fields" placement="right">
|
<Tooltip title="My Fields" placement="right">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
id="tour-field-list"
|
id="tour-field-list"
|
||||||
|
|
@ -376,7 +378,7 @@ export default function SideNavigator(props: Props) {
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{(isAg || isStreamline || user.hasFeature("admin")) && (
|
{(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
|
||||||
<Tooltip title="Grain Types" placement="right">
|
<Tooltip title="Grain Types" placement="right">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
id="tour-grain-types"
|
id="tour-grain-types"
|
||||||
|
|
@ -460,7 +462,7 @@ export default function SideNavigator(props: Props) {
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
{(isAg || isStreamline || user.hasFeature("admin")) &&
|
{(isAg || isIntel || isStreamline || user.hasFeature("admin")) &&
|
||||||
<Tooltip title="Marketplace" placement="right">
|
<Tooltip title="Marketplace" placement="right">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
id="tour-marketplace"
|
id="tour-marketplace"
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,7 @@ export default function Bin(props: Props) {
|
||||||
resp.data.devices.forEach((dev: any) => {
|
resp.data.devices.forEach((dev: any) => {
|
||||||
let device = Device.create(dev);
|
let device = Device.create(dev);
|
||||||
if (attachedDevIds.includes(device.id())) {
|
if (attachedDevIds.includes(device.id())) {
|
||||||
devs.push(Device.create(dev));
|
devs.push(device);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setDevices(devs);
|
setDevices(devs);
|
||||||
|
|
@ -676,6 +676,8 @@ export default function Bin(props: Props) {
|
||||||
userID={user.id()}
|
userID={user.id()}
|
||||||
components={components}
|
components={components}
|
||||||
setComponents={setComponents}
|
setComponents={setComponents}
|
||||||
|
componentDevices={componentDevices}
|
||||||
|
setComponentDevices={setComponentDevices}
|
||||||
updateBinStatus={updateStatus}
|
updateBinStatus={updateStatus}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ export interface IBinAPIContext {
|
||||||
removeComponent: (
|
removeComponent: (
|
||||||
bin: string,
|
bin: string,
|
||||||
component: string,
|
component: string,
|
||||||
|
device?: number,
|
||||||
otherTeam?: string
|
otherTeam?: string
|
||||||
) => Promise<AxiosResponse<pond.RemoveBinComponentResponse>>;
|
) => Promise<AxiosResponse<pond.RemoveBinComponentResponse>>;
|
||||||
removeAllComponents: (bin: string, otherTeam?: string) => Promise<AxiosResponse<pond.RemoveAllBinComponentsResponse>>;
|
removeAllComponents: (bin: string, otherTeam?: string) => Promise<AxiosResponse<pond.RemoveAllBinComponentsResponse>>;
|
||||||
|
|
@ -254,10 +255,11 @@ export default function BinProvider(props: PropsWithChildren<Props>) {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeComponent = (bin: string, component: string, otherTeam?: string) => {
|
const removeComponent = (bin: string, component: string, device?: number, otherTeam?: string) => {
|
||||||
const view = otherTeam ? otherTeam : as
|
const view = otherTeam ? otherTeam : as
|
||||||
let url = "/bins/" + bin + "/removeComponent/" + component;
|
let url = "/bins/" + bin + "/removeComponent/" + component;
|
||||||
if (view) url = url + "?as=" + view;
|
if (device) url = url + "?deviceToRemove=" + device
|
||||||
|
if (view) url = url + (device ? "&as=" : "?as=") + view;
|
||||||
return new Promise<AxiosResponse<pond.RemoveBinComponentResponse>>((resolve, reject)=>{
|
return new Promise<AxiosResponse<pond.RemoveBinComponentResponse>>((resolve, reject)=>{
|
||||||
post<pond.RemoveBinComponentResponse>(pondURL(url)).then(resp => {
|
post<pond.RemoveBinComponentResponse>(pondURL(url)).then(resp => {
|
||||||
resp.data = pond.RemoveBinComponentResponse.fromObject(resp.data)
|
resp.data = pond.RemoveBinComponentResponse.fromObject(resp.data)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ import MiVentLightLogo from "../assets/whitelabels/MiVent/lightLogo.png";
|
||||||
// import OmniAirLogo from "../assets/whitelabels/OmniAir/OmniAirLogo.png";
|
// import OmniAirLogo from "../assets/whitelabels/OmniAir/OmniAirLogo.png";
|
||||||
import MiPCALogo from "../assets/whitelabels/MiPCA/MiPCALogo.png";
|
import MiPCALogo from "../assets/whitelabels/MiPCA/MiPCALogo.png";
|
||||||
import StreamlineLogo from "../assets/whitelabels/Streamline/stream-logo.png"
|
import StreamlineLogo from "../assets/whitelabels/Streamline/stream-logo.png"
|
||||||
|
import IntellifarmsLogo from "../assets/whitelabels/Intellifarms/IFND-2023-Logo.png"
|
||||||
|
import IntellifarmsLogoWhite from "../assets/whitelabels/Intellifarms/IFND-2023-Logo-White.png"
|
||||||
// import { green, yellow } from "@mui/material/colors";
|
// import { green, yellow } from "@mui/material/colors";
|
||||||
|
|
||||||
const protips: string[] = [
|
const protips: string[] = [
|
||||||
|
|
@ -180,6 +182,32 @@ export function IsAdaptiveAgriculture(): boolean {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INTELLIFARMS_WHITE_LABEL: WhiteLabel = {
|
||||||
|
name: "Intellifarms",
|
||||||
|
primaryColour: "#9E1B32",
|
||||||
|
secondaryColour: "#4A4F55",
|
||||||
|
signatureColour: "#272727",
|
||||||
|
signatureAccentColour: "#fff",
|
||||||
|
auth0ClientId: import.meta.env.VITE_AUTH0_INTELLIFARMS_CLIENT_ID,
|
||||||
|
redirectOnLogout: true,
|
||||||
|
logoutRedirectTarget: "https://myintellifarms.com",
|
||||||
|
darkLogo: IntellifarmsLogo,
|
||||||
|
lightLogo: IntellifarmsLogoWhite,
|
||||||
|
transparentLogoBG: true,
|
||||||
|
blacklist: [],
|
||||||
|
docs: "Platform",
|
||||||
|
protips: protips,
|
||||||
|
homePage: "/bins"
|
||||||
|
};
|
||||||
|
|
||||||
|
export function IsIntellifarms(): boolean {
|
||||||
|
return (
|
||||||
|
getName() === "Intellifarms"
|
||||||
|
// window.location.origin.includes("staging") ||
|
||||||
|
// window.location.origin.includes("localhost")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function IsStreamline(): boolean {
|
export function IsStreamline(): boolean {
|
||||||
return (
|
return (
|
||||||
getName() === "Streamline"
|
getName() === "Streamline"
|
||||||
|
|
@ -324,7 +352,8 @@ const whitelabels = new Map<string, WhiteLabel>([
|
||||||
["adaptiveconstruction", ADAPTIVE_CONSTRUCTION_WHITE_LABEL],
|
["adaptiveconstruction", ADAPTIVE_CONSTRUCTION_WHITE_LABEL],
|
||||||
["omniair", OMNIAIR_WHITE_LABEL],
|
["omniair", OMNIAIR_WHITE_LABEL],
|
||||||
["mipca", MIPCA_WHITE_LABEL],
|
["mipca", MIPCA_WHITE_LABEL],
|
||||||
["mionetech", MIPCA_WHITE_LABEL]
|
["mionetech", MIPCA_WHITE_LABEL],
|
||||||
|
["intellifarms", INTELLIFARMS_WHITE_LABEL]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function getWhitelabel(): WhiteLabel {
|
export function getWhitelabel(): WhiteLabel {
|
||||||
|
|
@ -337,7 +366,7 @@ export function getWhitelabel(): WhiteLabel {
|
||||||
return BXT_WHITE_LABEL;
|
return BXT_WHITE_LABEL;
|
||||||
}
|
}
|
||||||
if (window.location.origin.includes("localhost")) {
|
if (window.location.origin.includes("localhost")) {
|
||||||
return STREAMLINE_WHITE_LABEL;
|
return INTELLIFARMS_WHITE_LABEL;
|
||||||
}
|
}
|
||||||
if (window.location.origin.includes("staging") || import.meta.env.VITE_LOCAL_STAGING=='true') {
|
if (window.location.origin.includes("staging") || import.meta.env.VITE_LOCAL_STAGING=='true') {
|
||||||
return STAGING_WHITELABEL;
|
return STAGING_WHITELABEL;
|
||||||
|
|
|
||||||