Merge branch 'co2_bin_activity' into dev_environment
This commit is contained in:
commit
8cfba48968
19 changed files with 358 additions and 141 deletions
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -10864,7 +10864,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/protobuf-ts": {
|
"node_modules/protobuf-ts": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#e5ab6369bbfc866e7e8792f94a62f1ca8931e67e",
|
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#8650e48a44dffc4510ab1fb1e869fd73e242db69",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"protobufjs": "^6.8.8"
|
"protobufjs": "^6.8.8"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"build:staging": "VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_APP_WS_URL=wss://stagingapi.brandxtech.ca/v1/live NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_STAGING} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP={OPEN_WEATHERMAP} vite build",
|
"build:staging": "VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_APP_WS_URL=wss://stagingapi.brandxtech.ca/v1/live NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_STAGING} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP=${OPEN_WEATHERMAP} vite build",
|
||||||
"build:development": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=https://bxt-dev.api.adaptiveagriculture.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_DEVELOPMENT} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP={OPEN_WEATHERMAP} vite build",
|
"build:development": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=https://bxt-dev.api.adaptiveagriculture.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_DEVELOPMENT} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP=${OPEN_WEATHERMAP} vite build",
|
||||||
"build:production": "VITE_AUTH0_CLIENT_ID=5pUCkl2SfogkWmM244UDcLEUOp8EFdHd VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=api.brandxtech.ca VITE_APP_API_URL=https://api.brandxtech.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_PRODUCTION} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP={OPEN_WEATHERMAP} vite build",
|
"build:production": "VITE_AUTH0_CLIENT_ID=5pUCkl2SfogkWmM244UDcLEUOp8EFdHd VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=api.brandxtech.ca VITE_APP_API_URL=https://api.brandxtech.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_PRODUCTION} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP=${OPEN_WEATHERMAP} vite build",
|
||||||
"build:offline": "npx env-cmd offline,whitelabel npm run build",
|
"build:offline": "npx env-cmd offline,whitelabel npm run build",
|
||||||
"test": "vitest"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ html, body, #root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* styles.css or a CSS module */
|
/* styles.css or a CSS module */
|
||||||
|
|
|
||||||
|
|
@ -58,9 +58,9 @@ interface GrainDetails {
|
||||||
export default function BinCard(props: Props) {
|
export default function BinCard(props: Props) {
|
||||||
const { bin, valDisplay } = props;
|
const { bin, valDisplay } = props;
|
||||||
const [cables, setCables] = useState<GrainCable[]>();
|
const [cables, setCables] = useState<GrainCable[]>();
|
||||||
const [modeDetails, setModeDetails] = useState("");
|
//const [modeDetails, setModeDetails] = useState("");
|
||||||
const [lidarPercentage, setLidarPercentage] = useState<number | undefined>();
|
const [lidarPercentage, setLidarPercentage] = useState<number | undefined>();
|
||||||
const [lidarBushels, setLidarBushels] = useState<number | undefined>();
|
//const [lidarBushels, setLidarBushels] = useState<number | undefined>();
|
||||||
const [{ user }] = useGlobalState();
|
const [{ user }] = useGlobalState();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [average, setAverage] = useState<GrainDetails>();
|
const [average, setAverage] = useState<GrainDetails>();
|
||||||
|
|
@ -84,6 +84,7 @@ export default function BinCard(props: Props) {
|
||||||
let coldestNode: GrainDetails | undefined;
|
let coldestNode: GrainDetails | undefined;
|
||||||
let hottestNode: GrainDetails | undefined;
|
let hottestNode: GrainDetails | undefined;
|
||||||
let now = moment();
|
let now = moment();
|
||||||
|
//loop through the cables in the bin status to prep for display
|
||||||
bin.status.grainCables.forEach(cable => {
|
bin.status.grainCables.forEach(cable => {
|
||||||
let c: GrainCable = new GrainCable();
|
let c: GrainCable = new GrainCable();
|
||||||
//flip the cables so that for display node 1 is at the bottom
|
//flip the cables so that for display node 1 is at the bottom
|
||||||
|
|
@ -137,8 +138,9 @@ export default function BinCard(props: Props) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastRead = moment(cable.lastRead);
|
//check cables missed readings
|
||||||
let elapsedMS = now.diff(lastRead);
|
let lastCableRead = moment(cable.lastRead);
|
||||||
|
let elapsedMS = now.diff(lastCableRead);
|
||||||
|
|
||||||
if (elapsedMS > cable.measurementInterval) {
|
if (elapsedMS > cable.measurementInterval) {
|
||||||
let missedReadings = Math.floor(elapsedMS / cable.measurementInterval);
|
let missedReadings = Math.floor(elapsedMS / cable.measurementInterval);
|
||||||
|
|
@ -147,6 +149,18 @@ export default function BinCard(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
//loop through the CO2's to check for missed readings
|
||||||
|
bin.status.co2.forEach(co2 => {
|
||||||
|
let lastRead = moment(co2.lastRead);
|
||||||
|
let elapsedMS = now.diff(lastRead);
|
||||||
|
if (elapsedMS > co2.measurementInterval) {
|
||||||
|
let missedReadings = Math.floor(elapsedMS / co2.measurementInterval);
|
||||||
|
if (missedReadings > mostMissedReadings) {
|
||||||
|
mostMissedReadings = missedReadings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
setCables(newGrainCables);
|
setCables(newGrainCables);
|
||||||
setMostMissed(mostMissedReadings);
|
setMostMissed(mostMissedReadings);
|
||||||
setColdNode(coldestNode);
|
setColdNode(coldestNode);
|
||||||
|
|
@ -165,28 +179,28 @@ export default function BinCard(props: Props) {
|
||||||
let ratio = 1 - cm / height;
|
let ratio = 1 - cm / height;
|
||||||
let capacity = or(bin.settings.specs?.bushelCapacity, 0);
|
let capacity = or(bin.settings.specs?.bushelCapacity, 0);
|
||||||
let lidarEstimate = Math.round(capacity * ratio);
|
let lidarEstimate = Math.round(capacity * ratio);
|
||||||
setLidarBushels(lidarEstimate);
|
//setLidarBushels(lidarEstimate);
|
||||||
setLidarPercentage(Math.round((lidarEstimate / capacity) * 100));
|
setLidarPercentage(Math.round((lidarEstimate / capacity) * 100));
|
||||||
}
|
}
|
||||||
}, [bin]);
|
}, [bin]);
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
let now = moment();
|
// let now = moment();
|
||||||
let duration = moment.duration(moment(bin.status.lastModeChange).diff(now));
|
// let duration = moment.duration(moment(bin.status.lastModeChange).diff(now));
|
||||||
let days = duration.asDays();
|
// let days = duration.asDays();
|
||||||
days = Math.abs(days);
|
// days = Math.abs(days);
|
||||||
duration.subtract(moment.duration(days, "days"));
|
// duration.subtract(moment.duration(days, "days"));
|
||||||
let hours = duration.hours();
|
// let hours = duration.hours();
|
||||||
hours = Math.abs(hours);
|
// hours = Math.abs(hours);
|
||||||
|
|
||||||
if (days > 50 && bin.settings.mode === pond.BinMode.BIN_MODE_DRYING) {
|
// if (days > 50 && bin.settings.mode === pond.BinMode.BIN_MODE_DRYING) {
|
||||||
setModeDetails("Calculating...");
|
// setModeDetails("Calculating...");
|
||||||
} else if (bin.settings.mode === pond.BinMode.BIN_MODE_NONE) {
|
// } else if (bin.settings.mode === pond.BinMode.BIN_MODE_NONE) {
|
||||||
setModeDetails("No Mode");
|
// setModeDetails("No Mode");
|
||||||
} else {
|
// } else {
|
||||||
setModeDetails(Math.floor(days) + "d " + hours + "h");
|
// setModeDetails(Math.floor(days) + "d " + hours + "h");
|
||||||
}
|
// }
|
||||||
}, [bin]);
|
// }, [bin]);
|
||||||
|
|
||||||
const typeDisplay = () => {
|
const typeDisplay = () => {
|
||||||
let grainType = bin.settings.inventory?.grainType ?? pond.Grain.GRAIN_NONE;
|
let grainType = bin.settings.inventory?.grainType ?? pond.Grain.GRAIN_NONE;
|
||||||
|
|
@ -440,9 +454,10 @@ export default function BinCard(props: Props) {
|
||||||
|
|
||||||
const componentStateBanner = () => {
|
const componentStateBanner = () => {
|
||||||
const hasCables = bin.status.grainCables.length > 0;
|
const hasCables = bin.status.grainCables.length > 0;
|
||||||
|
const hasCO2 = bin.status.co2.length > 0;
|
||||||
return (
|
return (
|
||||||
<Box display="flex" justifyContent="space-between">
|
<Box display="flex" justifyContent="space-between">
|
||||||
{hasCables ? (
|
{hasCables || hasCO2 ? (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Typography style={{ fontSize: 15, fontWeight: 650 }}>
|
<Typography style={{ fontSize: 15, fontWeight: 650 }}>
|
||||||
{mostMissed < 3 ? "Active" : mostMissed <= warningThreshold ? "Missing" : "Inactive"}
|
{mostMissed < 3 ? "Active" : mostMissed <= warningThreshold ? "Missing" : "Inactive"}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import { MoreVert } from "@mui/icons-material";
|
||||||
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
|
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
|
||||||
import { Ambient } from "models/Ambient";
|
import { Ambient } from "models/Ambient";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { CO2 } from "models/CO2";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
return ({
|
return ({
|
||||||
|
|
@ -123,7 +124,7 @@ export default function BinComponentTypes(props: Props) {
|
||||||
const [heaters, setHeaters] = useState<Controller[]>([]);
|
const [heaters, setHeaters] = useState<Controller[]>([]);
|
||||||
const [fans, setFans] = useState<Controller[]>([]);
|
const [fans, setFans] = useState<Controller[]>([]);
|
||||||
const [lidars, setLidars] = useState<Component[]>([]);
|
const [lidars, setLidars] = useState<Component[]>([]);
|
||||||
const [headspaceCo2s, setHeadspaceCo2s] = useState<Component[]>([]);
|
const [headspaceCo2s, setHeadspaceCo2s] = useState<CO2[]>([]);
|
||||||
const [tempUnit, setTempUnit] = useState<pond.TemperatureUnit>();
|
const [tempUnit, setTempUnit] = useState<pond.TemperatureUnit>();
|
||||||
const [pressureUnit, setPressureUnit] = useState<pond.PressureUnit>();
|
const [pressureUnit, setPressureUnit] = useState<pond.PressureUnit>();
|
||||||
const [selectedComponentKey, setSelectedComponentKey] = useState("");
|
const [selectedComponentKey, setSelectedComponentKey] = useState("");
|
||||||
|
|
@ -176,7 +177,7 @@ export default function BinComponentTypes(props: Props) {
|
||||||
var lidar: Component[] = [];
|
var lidar: Component[] = [];
|
||||||
var unassigned: Component[] = [];
|
var unassigned: Component[] = [];
|
||||||
var ambients: Ambient[] = [];
|
var ambients: Ambient[] = [];
|
||||||
var headspaceCo2s: Component[] = [];
|
var headspaceCo2s: CO2[] = [];
|
||||||
components.forEach(comp => {
|
components.forEach(comp => {
|
||||||
let pref = preferences.get(comp.key());
|
let pref = preferences.get(comp.key());
|
||||||
if (pref) {
|
if (pref) {
|
||||||
|
|
@ -198,7 +199,8 @@ export default function BinComponentTypes(props: Props) {
|
||||||
} else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_LIDAR) {
|
} else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_LIDAR) {
|
||||||
lidar.push(comp);
|
lidar.push(comp);
|
||||||
} else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_CO2) {
|
} else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_CO2) {
|
||||||
headspaceCo2s.push(comp);
|
let coComp = CO2.create(comp)
|
||||||
|
headspaceCo2s.push(coComp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -716,9 +718,8 @@ export default function BinComponentTypes(props: Props) {
|
||||||
return distanceCM + "cm";
|
return distanceCM + "cm";
|
||||||
};
|
};
|
||||||
|
|
||||||
const headspaceList = (sensors: Headspace[], lidars: Component[], headspaceCo2s: Component[]) => {
|
const headspaceList = (sensors: Headspace[], lidars: Component[], headspaceCo2s: CO2[]) => {
|
||||||
if (sensors.length < 1 && lidars.length < 1 && headspaceCo2s.length < 1) return null;
|
if (sensors.length < 1 && lidars.length < 1 && headspaceCo2s.length < 1) return null;
|
||||||
let headspaceComponents = lidars.concat(headspaceCo2s);
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ListSubheader component="div" disableGutters>
|
<ListSubheader component="div" disableGutters>
|
||||||
|
|
@ -778,7 +779,73 @@ export default function BinComponentTypes(props: Props) {
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{headspaceComponents.map((component, index) => {
|
{headspaceCo2s.map((co2, index) => {
|
||||||
|
let cIcon = GetComponentIcon(
|
||||||
|
co2.settings.type,
|
||||||
|
co2.settings.subtype,
|
||||||
|
theme.palette.mode
|
||||||
|
);
|
||||||
|
|
||||||
|
let colour = "white"; //default colour if there are no measurements to get the type from
|
||||||
|
|
||||||
|
let ppmString = "--";
|
||||||
|
if (
|
||||||
|
co2.status.measurement[0] &&
|
||||||
|
co2.status.measurement[0].values[0] &&
|
||||||
|
co2.status.measurement[0].values[0].values[0]
|
||||||
|
) {
|
||||||
|
colour = describeMeasurement(
|
||||||
|
co2.status.measurement[0].type,
|
||||||
|
co2.type(),
|
||||||
|
co2.subType()
|
||||||
|
).colour();
|
||||||
|
ppmString = co2.status.measurement[0].values[0].values[0] + " ppm";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ListItem key={index}>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar
|
||||||
|
variant="square"
|
||||||
|
src={cIcon}
|
||||||
|
alt={co2.name()}
|
||||||
|
style={{ width: theme.spacing(3), height: theme.spacing(3) }}
|
||||||
|
/>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText inset={cIcon === undefined}>{co2.name()}</ListItemText>
|
||||||
|
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Grid container direction="row" alignItems="center">
|
||||||
|
<Grid >
|
||||||
|
<Box className={classes.bgItem} padding={1}>
|
||||||
|
<div style={{ display: "flex", flexDirection: "row" }}>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
style={{
|
||||||
|
color: colour
|
||||||
|
}}>
|
||||||
|
{ppmString}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid >
|
||||||
|
<IconButton
|
||||||
|
onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
setComponentUnnassigned(false);
|
||||||
|
setSelectedComponentType(co2.type());
|
||||||
|
setSelectedComponentKey(co2.key());
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
}}>
|
||||||
|
<MoreVert />
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{lidars.map((component, index) => {
|
||||||
let cIcon = GetComponentIcon(
|
let cIcon = GetComponentIcon(
|
||||||
component.settings.type,
|
component.settings.type,
|
||||||
component.settings.subtype,
|
component.settings.subtype,
|
||||||
|
|
|
||||||
|
|
@ -540,8 +540,7 @@ export default function BinSettings(props: Props) {
|
||||||
let specs = form.specs;
|
let specs = form.specs;
|
||||||
let specsValid =
|
let specsValid =
|
||||||
specs && Number(inputCapacity) > 0 && specs.diameterCm > 0 && specs.heightCm > 0;
|
specs && Number(inputCapacity) > 0 && specs.diameterCm > 0 && specs.heightCm > 0;
|
||||||
let tempsValid = +formExtension.highTemp > +formExtension.lowTemp;
|
return specsValid
|
||||||
return specsValid && tempsValid;
|
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -1415,20 +1414,6 @@ export default function BinSettings(props: Props) {
|
||||||
let angle = RadiansToDegrees(Math.atan((coneCM ?? 0) / (dCm / 2))); //('arcTan(opposite / adjacent))' and convert to degrees
|
let angle = RadiansToDegrees(Math.atan((coneCM ?? 0) / (dCm / 2))); //('arcTan(opposite / adjacent))' and convert to degrees
|
||||||
angle = Math.round(angle * 100) / 100; // round to 2 digits
|
angle = Math.round(angle * 100) / 100; // round to 2 digits
|
||||||
|
|
||||||
//update the form with the new specs
|
|
||||||
updateForm(
|
|
||||||
"specs",
|
|
||||||
pond.BinSpecs.create({
|
|
||||||
...form.specs,
|
|
||||||
modelId: 0,
|
|
||||||
advancedDimensions: pond.BinAdvancedDimensions.create({
|
|
||||||
...form.specs?.advancedDimensions,
|
|
||||||
topConeHeight: coneCM,
|
|
||||||
roofAngle: angle
|
|
||||||
})
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let ext = cloneDeep(formExtension);
|
let ext = cloneDeep(formExtension);
|
||||||
//calculate the total height from the parts
|
//calculate the total height from the parts
|
||||||
let s = isNaN(parseFloat(sidewallHeight)) ? 0 : parseFloat(sidewallHeight);
|
let s = isNaN(parseFloat(sidewallHeight)) ? 0 : parseFloat(sidewallHeight);
|
||||||
|
|
@ -1448,6 +1433,29 @@ export default function BinSettings(props: Props) {
|
||||||
|
|
||||||
//update the extensions with the new values
|
//update the extensions with the new values
|
||||||
setFormExtension({ ...ext });
|
setFormExtension({ ...ext });
|
||||||
|
|
||||||
|
//need to get the total height in cm
|
||||||
|
let totalHeightCM = s + t + h; //the total height in the users units
|
||||||
|
if(getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
|
||||||
|
totalHeightCM = totalHeightCM * 30.48 //convert from feet to cm
|
||||||
|
}else{
|
||||||
|
totalHeightCM = totalHeightCM * 100 //convert from m to cm
|
||||||
|
}
|
||||||
|
|
||||||
|
//update the form with the new specs
|
||||||
|
updateForm(
|
||||||
|
"specs",
|
||||||
|
pond.BinSpecs.create({
|
||||||
|
...form.specs,
|
||||||
|
modelId: 0,
|
||||||
|
heightCm: Math.round(totalHeightCM),
|
||||||
|
advancedDimensions: pond.BinAdvancedDimensions.create({
|
||||||
|
...form.specs?.advancedDimensions,
|
||||||
|
topConeHeight: coneCM,
|
||||||
|
roofAngle: angle
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
|
|
@ -1497,10 +1505,18 @@ export default function BinSettings(props: Props) {
|
||||||
setInputCapacity(bushelCap.toFixed(0));
|
setInputCapacity(bushelCap.toFixed(0));
|
||||||
ext.capacity = bushelCap.toFixed(0);
|
ext.capacity = bushelCap.toFixed(0);
|
||||||
ext.topConeHeight = newConeHeight;
|
ext.topConeHeight = newConeHeight;
|
||||||
ext.height = (s + t + h).toFixed(2);
|
ext.height = (s + t + h).toFixed(2)
|
||||||
|
|
||||||
setFormExtension(ext);
|
setFormExtension(ext);
|
||||||
|
|
||||||
|
//need to get the total height in cm
|
||||||
|
let totalHeightCM = s + t + h; //the total height in the users units
|
||||||
|
if(getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
|
||||||
|
totalHeightCM = totalHeightCM * 30.48 //convert from feet to cm
|
||||||
|
}else{
|
||||||
|
totalHeightCM = totalHeightCM * 100 //convert from m to cm
|
||||||
|
}
|
||||||
|
|
||||||
setModelID(0);
|
setModelID(0);
|
||||||
//update the form with the new specs
|
//update the form with the new specs
|
||||||
updateForm(
|
updateForm(
|
||||||
|
|
@ -1508,6 +1524,7 @@ export default function BinSettings(props: Props) {
|
||||||
pond.BinSpecs.create({
|
pond.BinSpecs.create({
|
||||||
...form.specs,
|
...form.specs,
|
||||||
modelId: 0,
|
modelId: 0,
|
||||||
|
heightCm: Math.round(totalHeightCM),
|
||||||
advancedDimensions: pond.BinAdvancedDimensions.create({
|
advancedDimensions: pond.BinAdvancedDimensions.create({
|
||||||
...form.specs?.advancedDimensions,
|
...form.specs?.advancedDimensions,
|
||||||
roofAngle: angle ?? 0,
|
roofAngle: angle ?? 0,
|
||||||
|
|
@ -1553,11 +1570,21 @@ export default function BinSettings(props: Props) {
|
||||||
setFormExtension({ ...ext });
|
setFormExtension({ ...ext });
|
||||||
|
|
||||||
setModelID(0);
|
setModelID(0);
|
||||||
|
|
||||||
|
//need to get the total height in cm
|
||||||
|
let totalHeightCM = s + t + h; //the total height in the users units
|
||||||
|
if(getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
|
||||||
|
totalHeightCM = totalHeightCM * 30.48 //convert from feet to cm
|
||||||
|
}else{
|
||||||
|
totalHeightCM = totalHeightCM * 100 //convert from m to cm
|
||||||
|
}
|
||||||
|
|
||||||
updateForm(
|
updateForm(
|
||||||
"specs",
|
"specs",
|
||||||
pond.BinSpecs.create({
|
pond.BinSpecs.create({
|
||||||
...form.specs,
|
...form.specs,
|
||||||
modelId: 0,
|
modelId: 0,
|
||||||
|
heightCm: Math.round(totalHeightCM),
|
||||||
advancedDimensions: pond.BinAdvancedDimensions.create({
|
advancedDimensions: pond.BinAdvancedDimensions.create({
|
||||||
...form.specs?.advancedDimensions,
|
...form.specs?.advancedDimensions,
|
||||||
sidewallHeight:
|
sidewallHeight:
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import React, { useEffect, useState } from "react";
|
||||||
import { avg, getTemperatureUnit } from "utils";
|
import { avg, getTemperatureUnit } from "utils";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import { Mark } from "@mui/material/Slider/useSlider.types";
|
import { Mark } from "@mui/material/Slider/useSlider.types";
|
||||||
|
import { CO2 } from "models/CO2";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
return ({
|
return ({
|
||||||
|
|
@ -117,7 +118,7 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
bin: Bin;
|
bin: Bin;
|
||||||
headspaceCO2: Component[];
|
headspaceCO2: CO2[];
|
||||||
cables: GrainCable[];
|
cables: GrainCable[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -162,8 +162,9 @@ export default function BinTransactions(props: Props){
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
openSnack("There was a problem submitting transaction approval")
|
openSnack("There was a problem submitting transaction approval")
|
||||||
})
|
}).finally(() => {
|
||||||
refresh()
|
refresh()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,9 +175,10 @@ export default function BinTransactions(props: Props){
|
||||||
openSnack("Transaction rejected")
|
openSnack("Transaction rejected")
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
openSnack("There was a problem rejecting the transaction")
|
openSnack("There was a problem rejecting the transaction")
|
||||||
|
}).finally(() => {
|
||||||
|
refresh()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
refresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOptions = (type: pond.ObjectType) => {
|
const getOptions = (type: pond.ObjectType) => {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"ID": 1,
|
"ID": 1,
|
||||||
"Name": "CABL-BRK1",
|
"Name": "4-Rib",
|
||||||
"DealerPrice": 210
|
"DealerPrice": 165
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ID": 2,
|
"ID": 2,
|
||||||
"Name": "CABL-BRK2",
|
"Name": "4-Rib",
|
||||||
"DealerPrice": 217.5
|
"DealerPrice": 165
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ID": 3,
|
"ID": 3,
|
||||||
"Name": "CABL-BRK3",
|
"Name": "2-Rib",
|
||||||
"DealerPrice": 87
|
"DealerPrice": 90
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useMobile } from "hooks";
|
import { useMobile } from "hooks";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import { getThemeType } from "theme";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
//const isMobile = useMobile()
|
//const isMobile = useMobile()
|
||||||
|
|
@ -56,6 +57,12 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
backgroundColor: "rgba(150, 150, 150, 0.15)",
|
backgroundColor: "rgba(150, 150, 150, 0.15)",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
stickyHeader: {
|
||||||
|
position: "sticky",
|
||||||
|
backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(40, 40, 40)",
|
||||||
|
top: 0,
|
||||||
|
zIndex: 300 //giving a really high z-index to make sure nothing is in front of it
|
||||||
|
}
|
||||||
})},
|
})},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -101,6 +108,7 @@ interface Props<T> {
|
||||||
endTitleElement?: string | JSX.Element | (() => string | JSX.Element);
|
endTitleElement?: string | JSX.Element | (() => string | JSX.Element);
|
||||||
loadMore?: () => void;
|
loadMore?: () => void;
|
||||||
resizeable?: boolean;
|
resizeable?: boolean;
|
||||||
|
stickyHeader?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
|
|
@ -136,6 +144,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
setOrder,
|
setOrder,
|
||||||
loadMore,
|
loadMore,
|
||||||
// resizeable,
|
// resizeable,
|
||||||
|
stickyHeader
|
||||||
} = props
|
} = props
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const columns = typeof(props.columns) === "function" ? props.columns() : props.columns
|
const columns = typeof(props.columns) === "function" ? props.columns() : props.columns
|
||||||
|
|
@ -486,9 +495,9 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer className={classes.tableContainer} component={Paper}>
|
<Box className={classes.tableContainer} component={Paper}>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead className={stickyHeader ? classes.stickyHeader : undefined}>
|
||||||
<TableRow sx={ !title && !setSearchText ? { display: "none" } : undefined}>
|
<TableRow sx={ !title && !setSearchText ? { display: "none" } : undefined}>
|
||||||
<TableCell colSpan={10000} sx={{ border: "none" }}>
|
<TableCell colSpan={10000} sx={{ border: "none" }}>
|
||||||
<Grid2 container justifyContent="space-between">
|
<Grid2 container justifyContent="space-between">
|
||||||
|
|
@ -702,6 +711,6 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
</Typography>
|
</Typography>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</TableContainer>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -27,6 +27,7 @@ import DeviceIcon from "products/Bindapt/BindaptIcon";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
//import { useHistory } from "react-router";
|
//import { useHistory } from "react-router";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => ({
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
iconButtons: {
|
iconButtons: {
|
||||||
|
|
@ -99,11 +100,11 @@ export default function AgMapTools(props: Props) {
|
||||||
const [binControlHovered, setBinControlHovered] = useState(false);
|
const [binControlHovered, setBinControlHovered] = useState(false);
|
||||||
const [scoutingControlHovered, setScoutingControlHovered] = useState(false);
|
const [scoutingControlHovered, setScoutingControlHovered] = useState(false);
|
||||||
const [showFieldTitle, setShowFieldTitles] = useState(false);
|
const [showFieldTitle, setShowFieldTitles] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
//const history = useHistory();
|
//const history = useHistory();
|
||||||
|
|
||||||
const goToMyFields = () => {
|
const goToMyFields = () => {
|
||||||
console.log("navigate to my fields page")
|
navigate("/fields");
|
||||||
//history.push("/myFields");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleControls = (control: "field" | "bin" | "scout") => {
|
const toggleControls = (control: "field" | "bin" | "scout") => {
|
||||||
|
|
|
||||||
110
src/models/CO2.ts
Normal file
110
src/models/CO2.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { quack } from "protobuf-ts/pond";
|
||||||
|
import { or } from "utils/types";
|
||||||
|
import { clone, cloneDeep } from "lodash";
|
||||||
|
import { Component } from "models";
|
||||||
|
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
|
||||||
|
import { extractGrainCable } from "pbHelpers/ComponentTypes";
|
||||||
|
|
||||||
|
export class CO2 {
|
||||||
|
public settings: pond.ComponentSettings = pond.ComponentSettings.create();
|
||||||
|
public status: pond.ComponentStatus = pond.ComponentStatus.create();
|
||||||
|
public lastReading: string = "";
|
||||||
|
public ppm: number = 0;
|
||||||
|
|
||||||
|
public static create(comp: Component): CO2 {
|
||||||
|
let my = new CO2();
|
||||||
|
my.settings = comp.settings;
|
||||||
|
my.status = comp.status;
|
||||||
|
|
||||||
|
if (comp.status.lastGoodMeasurement.length > 0) {
|
||||||
|
comp.status.lastGoodMeasurement.forEach(um => {
|
||||||
|
if (um.timestamps[0]) {
|
||||||
|
my.lastReading = um.timestamps[0];
|
||||||
|
}
|
||||||
|
if (um.values[0] && um.values[0].values.length > 0) {
|
||||||
|
if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_PPM) {
|
||||||
|
my.ppm = um.values[0].values[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return my;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createPond(comp: pond.Component): CO2 {
|
||||||
|
let my = new CO2();
|
||||||
|
my.settings = comp.settings ? comp.settings : pond.ComponentSettings.create();
|
||||||
|
my.status = comp.status ? comp.status : pond.ComponentStatus.create();
|
||||||
|
|
||||||
|
if (comp.status && comp.status.lastGoodMeasurement.length > 0) {
|
||||||
|
comp.status.lastGoodMeasurement.forEach(um => {
|
||||||
|
if (um.timestamps[0]) {
|
||||||
|
my.lastReading = um.timestamps[0];
|
||||||
|
}
|
||||||
|
if (um.values[0] && um.values[0].values.length > 0) {
|
||||||
|
if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_PPM) {
|
||||||
|
my.ppm = um.values[0].values[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return my;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static any(data: any): CO2 {
|
||||||
|
let comp = pond.Component.fromObject(cloneDeep(data));
|
||||||
|
let my = CO2.createPond(comp);
|
||||||
|
if (data && data.status && data.status.lastMeasurement) {
|
||||||
|
my.status.lastMeasurement = data.status.lastMeasurement;
|
||||||
|
}
|
||||||
|
return my;
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(other: CO2) {
|
||||||
|
this.settings = other.settings;
|
||||||
|
this.status = other.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public name(): string {
|
||||||
|
return this.settings.name !== "" ? this.settings.name : "Component " + this.key();
|
||||||
|
}
|
||||||
|
|
||||||
|
public key(): string {
|
||||||
|
return this.settings.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public location(): quack.ComponentID {
|
||||||
|
return quack.ComponentID.fromObject({
|
||||||
|
type: this.settings.type,
|
||||||
|
addressType: this.settings.addressType,
|
||||||
|
address: this.settings.address
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public locationString(): string {
|
||||||
|
return (
|
||||||
|
or(this.settings.type, 0).toString() +
|
||||||
|
"-" +
|
||||||
|
or(this.settings.addressType, 0).toString() +
|
||||||
|
"-" +
|
||||||
|
or(this.settings.address, 0).toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public type(): quack.ComponentType {
|
||||||
|
return this.settings.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public subType(): number {
|
||||||
|
return this.settings.subtype;
|
||||||
|
}
|
||||||
|
|
||||||
|
//return the CO2 as a component
|
||||||
|
public asComponent(): Component {
|
||||||
|
let component = Component.create();
|
||||||
|
component.settings = this.settings;
|
||||||
|
component.status = this.status;
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ export default function AviationMap() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer padding={-1}>
|
<PageContainer>
|
||||||
{location.state !== undefined &&
|
{location.state !== undefined &&
|
||||||
location.state !== null &&
|
location.state !== null &&
|
||||||
location.state.long !== undefined &&
|
location.state.long !== undefined &&
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ import { Controller } from "models/Controller";
|
||||||
import TaskViewer from "tasks/TaskViewer";
|
import TaskViewer from "tasks/TaskViewer";
|
||||||
import ButtonGroup from "common/ButtonGroup";
|
import ButtonGroup from "common/ButtonGroup";
|
||||||
import BinTransactions from "bin/BinTransactions";
|
import BinTransactions from "bin/BinTransactions";
|
||||||
|
import { CO2 } from "models/CO2";
|
||||||
|
|
||||||
interface TabPanelProps {
|
interface TabPanelProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|
@ -192,7 +193,7 @@ export default function Bin(props: Props) {
|
||||||
const [ambients, setAmbients] = useState<Ambient[]>([]);
|
const [ambients, setAmbients] = useState<Ambient[]>([]);
|
||||||
const [grainCables, setGrainCables] = useState<GrainCable[]>([]);
|
const [grainCables, setGrainCables] = useState<GrainCable[]>([]);
|
||||||
const [pressures, setPressures] = useState<Pressure[]>([]);
|
const [pressures, setPressures] = useState<Pressure[]>([]);
|
||||||
const [headspaceCO2, setHeadspaceCO2] = useState<Component[]>([]);
|
const [headspaceCO2, setHeadspaceCO2] = useState<CO2[]>([]);
|
||||||
const [heaters, setHeaters] = useState<Controller[]>([]);
|
const [heaters, setHeaters] = useState<Controller[]>([]);
|
||||||
const [fans, setFans] = useState<Controller[]>([]);
|
const [fans, setFans] = useState<Controller[]>([]);
|
||||||
const [detail, setDetail] = useState<
|
const [detail, setDetail] = useState<
|
||||||
|
|
@ -210,61 +211,12 @@ export default function Bin(props: Props) {
|
||||||
new Map<string, number>()
|
new Map<string, number>()
|
||||||
);
|
);
|
||||||
const [binPresets, setBinPresets] = useState<DevicePreset[]>([]);
|
const [binPresets, setBinPresets] = useState<DevicePreset[]>([]);
|
||||||
const [missedCableReadings, setMissedCableReadings] = useState(0);
|
const [missedReadings, setMissedReadings] = useState(0);
|
||||||
|
|
||||||
const handleChange = (_event: React.ChangeEvent<{}>, newValue: number) => {
|
const handleChange = (_event: React.ChangeEvent<{}>, newValue: number) => {
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
// const StyledToggle = styled(ToggleButton)(() => ({
|
|
||||||
// root: {
|
|
||||||
// backgroundColor: "transparent",
|
|
||||||
// overflow: "visible",
|
|
||||||
// content: "content-box",
|
|
||||||
// "&$selected": {
|
|
||||||
// backgroundColor: "gold",
|
|
||||||
// color: "black",
|
|
||||||
// borderRadius: 24,
|
|
||||||
// fontWeight: "bold"
|
|
||||||
// },
|
|
||||||
// "&$selected:hover": {
|
|
||||||
// backgroundColor: "rgb(255, 255, 0)",
|
|
||||||
// color: "black",
|
|
||||||
// borderRadius: 24
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// selected: {}
|
|
||||||
// }))
|
|
||||||
|
|
||||||
// const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }: { theme: Theme }) => ({
|
|
||||||
// grouped: {
|
|
||||||
// margin: theme.spacing(-0.5),
|
|
||||||
// border: "none",
|
|
||||||
// padding: theme.spacing(1),
|
|
||||||
// "&:not(:first-child):not(:last-child)": {
|
|
||||||
// borderRadius: 24,
|
|
||||||
// marginRight: theme.spacing(0.5),
|
|
||||||
// marginLeft: theme.spacing(0.5)
|
|
||||||
// },
|
|
||||||
// "&:first-child": {
|
|
||||||
// borderRadius: 24,
|
|
||||||
// marginLeft: theme.spacing(0.25)
|
|
||||||
// },
|
|
||||||
// "&:last-child": {
|
|
||||||
// borderRadius: 24,
|
|
||||||
// marginRight: theme.spacing(0.25)
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// root: {
|
|
||||||
// backgroundColor: darken(
|
|
||||||
// theme.palette.background.paper,
|
|
||||||
// getThemeType() === "light" ? 0.05 : 0.25
|
|
||||||
// ),
|
|
||||||
// borderRadius: 24,
|
|
||||||
// content: "border-box"
|
|
||||||
// }
|
|
||||||
// }))
|
|
||||||
|
|
||||||
const load = useCallback(() => {
|
const load = useCallback(() => {
|
||||||
if (loadRef.current || user.id() === "") return;
|
if (loadRef.current || user.id() === "") return;
|
||||||
setBinLoading(true);
|
setBinLoading(true);
|
||||||
|
|
@ -364,9 +316,10 @@ export default function Bin(props: Props) {
|
||||||
var fans: Controller[] = [];
|
var fans: Controller[] = [];
|
||||||
var heaters: Controller[] = [];
|
var heaters: Controller[] = [];
|
||||||
var pressures: Pressure[] = [];
|
var pressures: Pressure[] = [];
|
||||||
var headspaceCo2: Component[] = [];
|
var headspaceCo2: CO2[] = [];
|
||||||
var mostMissed: number = 0;
|
var mostMissed: number = 0;
|
||||||
let now = moment();
|
let now = moment();
|
||||||
|
|
||||||
components.forEach(comp => {
|
components.forEach(comp => {
|
||||||
let pref = preferences.get(comp.key());
|
let pref = preferences.get(comp.key());
|
||||||
if (pref) {
|
if (pref) {
|
||||||
|
|
@ -395,7 +348,17 @@ export default function Bin(props: Props) {
|
||||||
pressures.push(Pressure.create(comp));
|
pressures.push(Pressure.create(comp));
|
||||||
if (pref.type === pond.BinComponent.BIN_COMPONENT_HEADSPACE) {
|
if (pref.type === pond.BinComponent.BIN_COMPONENT_HEADSPACE) {
|
||||||
if (comp.type() === quack.ComponentType.COMPONENT_TYPE_CO2) {
|
if (comp.type() === quack.ComponentType.COMPONENT_TYPE_CO2) {
|
||||||
headspaceCo2.push(comp);
|
//check if there are missed readings from the co2 and compare them to what we already had
|
||||||
|
let coComp = CO2.create(comp)
|
||||||
|
let lastRead = moment(coComp.lastReading);
|
||||||
|
let elapsedMS = now.diff(lastRead);
|
||||||
|
if (elapsedMS > coComp.settings.measurementPeriodMs) {
|
||||||
|
let missedReadings = Math.floor(elapsedMS / coComp.settings.measurementPeriodMs);
|
||||||
|
if (missedReadings > mostMissed) {
|
||||||
|
mostMissed = missedReadings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headspaceCo2.push(coComp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -403,7 +366,7 @@ export default function Bin(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setMissedCableReadings(mostMissed);
|
setMissedReadings(mostMissed);
|
||||||
setPlenums(plenums);
|
setPlenums(plenums);
|
||||||
setGrainCables(grainCables);
|
setGrainCables(grainCables);
|
||||||
setPressures(pressures);
|
setPressures(pressures);
|
||||||
|
|
@ -1097,8 +1060,12 @@ export default function Bin(props: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const componentState = () => {
|
const componentState = () => {
|
||||||
|
const hasCables = grainCables.length > 0
|
||||||
|
const hasCO2 = headspaceCO2.length > 0
|
||||||
return (
|
return (
|
||||||
<Box display="flex" alignContent="center" alignItems="center">
|
<Box display="flex" alignContent="center" alignItems="center">
|
||||||
|
{hasCables || hasCO2 ?
|
||||||
|
<React.Fragment>
|
||||||
<Typography
|
<Typography
|
||||||
align="center"
|
align="center"
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -1108,25 +1075,39 @@ export default function Bin(props: Props) {
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
color: "black"
|
color: "black"
|
||||||
}}>
|
}}>
|
||||||
{missedCableReadings < 3
|
{missedReadings < 3
|
||||||
? "Active"
|
? "Active"
|
||||||
: missedCableReadings <= warningThreshold
|
: missedReadings <= warningThreshold
|
||||||
? "Missing"
|
? "Missing"
|
||||||
: "Inactive"}
|
: "Inactive"}
|
||||||
</Typography>
|
</Typography>
|
||||||
{missedCableReadings < 3 ? (
|
{missedReadings < 3 ? (
|
||||||
<CheckCircle style={{ color: "green" }} />
|
<CheckCircle style={{ color: "green" }} />
|
||||||
) : missedCableReadings <= warningThreshold ? (
|
) : missedReadings <= warningThreshold ? (
|
||||||
<Warning style={{ color: "yellow" }} />
|
<Warning style={{ color: "yellow" }} />
|
||||||
) : (
|
) : (
|
||||||
<Warning style={{ color: "red" }} />
|
<Warning style={{ color: "red" }} />
|
||||||
)}
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
:
|
||||||
|
<Typography
|
||||||
|
align="center"
|
||||||
|
style={{
|
||||||
|
marginRight: 10,
|
||||||
|
fontWeight: 650,
|
||||||
|
fontSize: 15,
|
||||||
|
textAlign: "center",
|
||||||
|
color: "black"
|
||||||
|
}}>
|
||||||
|
Unmonitored
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer padding={1}>
|
<PageContainer spacing={1}>
|
||||||
{showPlenumError()}
|
{showPlenumError()}
|
||||||
<Box
|
<Box
|
||||||
paddingBottom={isMobile || displayMobile ? 1 : 2}
|
paddingBottom={isMobile || displayMobile ? 1 : 2}
|
||||||
|
|
|
||||||
|
|
@ -1477,5 +1477,5 @@ export default function Bins(props: Props) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return !props.insert ? <PageContainer padding={-1}>{page()}</PageContainer> : page();
|
return !props.insert ? <PageContainer>{page()}</PageContainer> : page();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export default function ConstructionSiteMap() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer padding={-1}>
|
<PageContainer>
|
||||||
{location.state !== undefined &&
|
{location.state !== undefined &&
|
||||||
location.state !== null &&
|
location.state !== null &&
|
||||||
location.state.long !== undefined &&
|
location.state.long !== undefined &&
|
||||||
|
|
|
||||||
|
|
@ -718,7 +718,7 @@ export default function Devices() {
|
||||||
)
|
)
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<PageContainer padding={isMobile ? 0 : 2}>
|
<PageContainer spacing={2}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
|
|
@ -749,6 +749,7 @@ export default function Devices() {
|
||||||
<ResponsiveTable<Device>
|
<ResponsiveTable<Device>
|
||||||
title={<SmartBreadcrumb prependPaths={getGroup() && ["groups", getGroup().id().toString()]} groupName={getGroup() && getGroup().name()} paddingBottom={1}/>}
|
title={<SmartBreadcrumb prependPaths={getGroup() && ["groups", getGroup().id().toString()]} groupName={getGroup() && getGroup().name()} paddingBottom={1}/>}
|
||||||
subtitle={subtitle}
|
subtitle={subtitle}
|
||||||
|
stickyHeader
|
||||||
rows={devices}
|
rows={devices}
|
||||||
renderGutter={ isMobile ? gutter : undefined }
|
renderGutter={ isMobile ? gutter : undefined }
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { useLocation } from "react-router";
|
||||||
export default function FieldMap() {
|
export default function FieldMap() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
return (
|
return (
|
||||||
<PageContainer padding={-1}>
|
<PageContainer>
|
||||||
{location.state !== undefined &&
|
{location.state !== undefined &&
|
||||||
location.state !== null &&
|
location.state !== null &&
|
||||||
location.state.long !== undefined &&
|
location.state.long !== undefined &&
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ interface Props extends PropsWithChildren{
|
||||||
fullViewport?: boolean;
|
fullViewport?: boolean;
|
||||||
isCenterCenter?: boolean;
|
isCenterCenter?: boolean;
|
||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
padding?: number;
|
spacing?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PageContainer: React.FunctionComponent<Props> = (props: Props) => {
|
export const PageContainer: React.FunctionComponent<Props> = (props: Props) => {
|
||||||
|
|
@ -48,7 +48,7 @@ export const PageContainer: React.FunctionComponent<Props> = (props: Props) => {
|
||||||
const { children, fullViewport, isCenterCenter, sx } = props;
|
const { children, fullViewport, isCenterCenter, sx } = props;
|
||||||
// let fullViewport = false
|
// let fullViewport = false
|
||||||
// let isCenterCenter = false
|
// let isCenterCenter = false
|
||||||
const padding = props.padding ? props.padding : isMobile ? 1 : 2;
|
const spacing = props.spacing;
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
|
@ -57,7 +57,9 @@ export const PageContainer: React.FunctionComponent<Props> = (props: Props) => {
|
||||||
)}
|
)}
|
||||||
sx={{
|
sx={{
|
||||||
...sx, // Spread existing sx
|
...sx, // Spread existing sx
|
||||||
...(padding !== undefined && { padding }), // Conditionally add padding
|
paddingX: spacing,
|
||||||
|
marginTop: spacing
|
||||||
|
// ...(padding !== undefined && { padding }), // Conditionally add padding
|
||||||
}}
|
}}
|
||||||
disableGutters
|
disableGutters
|
||||||
maxWidth={false}
|
maxWidth={false}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue