Merge branch 'staging_environment' into 3d_bin

This commit is contained in:
csawatzky 2026-04-15 11:33:52 -06:00
commit ff64f8594c
27 changed files with 219 additions and 42 deletions

1
.env
View file

@ -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"

View file

@ -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:

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View 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"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View file

@ -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) {

View file

@ -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>

View file

@ -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}

View file

@ -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",

View file

@ -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")}

View file

@ -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()} />

View file

@ -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"

View file

@ -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>

View file

@ -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)

View file

@ -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;