merged websocket implementation
This commit is contained in:
commit
06c26f3262
41 changed files with 1181 additions and 468 deletions
|
|
@ -27,7 +27,7 @@ const useStyles = makeStyles((theme: Theme) => {
|
|||
},
|
||||
position: "fixed",
|
||||
bottom: theme.spacing(8), //for mobile navigator
|
||||
right: theme.spacing(10.25),
|
||||
right: theme.spacing(2),
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
bottom: theme.spacing(1.75)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -443,14 +443,24 @@ export default function BinCard(props: Props) {
|
|||
" L"
|
||||
);
|
||||
}
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && bin.bushelsPerTonne() > 1) {
|
||||
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
|
||||
return (
|
||||
bin.grainTonnes().toLocaleString() +
|
||||
bin.grainInventory().toLocaleString() +
|
||||
" mT " +
|
||||
bin.fillPercent() +
|
||||
"%"
|
||||
);
|
||||
} else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
|
||||
return (
|
||||
bin.grainInventory().toLocaleString() +
|
||||
" t " +
|
||||
bin.fillPercent() +
|
||||
"%"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
current.toLocaleString() +
|
||||
"/" +
|
||||
|
|
|
|||
|
|
@ -126,14 +126,13 @@ interface BinFormExtension {
|
|||
hopperHeight: string;
|
||||
diameter: string;
|
||||
grainBushels: string;
|
||||
grainTonnes: string;
|
||||
grainWeight: string;
|
||||
grainType: Option | null;
|
||||
grainUse: Option | null;
|
||||
grainSubtype: string;
|
||||
customTypeName: string;
|
||||
highTemp: number;
|
||||
lowTemp: number;
|
||||
bushelsPerTonne: string;
|
||||
tempTarget: number;
|
||||
}
|
||||
|
||||
|
|
@ -160,14 +159,13 @@ export default function BinSettings(props: Props) {
|
|||
hopperHeight: "",
|
||||
diameter: "",
|
||||
grainBushels: "",
|
||||
grainTonnes: "",
|
||||
grainWeight: "",
|
||||
grainType: null,
|
||||
grainUse: null,
|
||||
grainSubtype: "",
|
||||
customTypeName: "",
|
||||
highTemp: 20,
|
||||
lowTemp: 10,
|
||||
bushelsPerTonne: "",
|
||||
tempTarget: 15
|
||||
});
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
|
@ -194,6 +192,7 @@ export default function BinSettings(props: Props) {
|
|||
const [isCustomBin, setIsCustomBin] = useState(false);
|
||||
const [binModelOps, setBinModelOptions] = useState<Option[]>([]);
|
||||
const [selectedBinModel, setSelectedBinModel] = useState<Option | null>(null);
|
||||
const [bushelConversion, setBushelConversion] = useState(0) //the number used to convert the bushels to either tonne or ton based on user preference
|
||||
const [modelID, setModelID] = useState(0);
|
||||
const [autoTopNode, setAutoTopNode] = useState(false);
|
||||
const [inventoryControl, setInventoryControl] = useState<pond.BinInventoryControl>(
|
||||
|
|
@ -232,14 +231,27 @@ export default function BinSettings(props: Props) {
|
|||
|
||||
if (initForm.inventory) {
|
||||
setInventoryControl(initForm.inventory.inventoryControl);
|
||||
//make sure bushels per tonne is set so bins that don't have it in the settings yet can still do it before they get updated properly
|
||||
if (!initForm.inventory.bushelsPerTonne) {
|
||||
initForm.inventory.bushelsPerTonne = GrainDescriber(
|
||||
//make sure bushels per ton/ne is set so bins that don't have it in the settings yet can still do it before they get updated properly
|
||||
const grain = GrainDescriber(
|
||||
initForm.inventory.grainType
|
||||
).bushelsPerTonne;
|
||||
)
|
||||
|
||||
if (!initForm.inventory.bushelsPerTonne) {
|
||||
initForm.inventory.bushelsPerTonne = grain.bushelsPerTonne;
|
||||
}
|
||||
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
||||
setBushelConversion(initForm.inventory.bushelsPerTonne)
|
||||
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
setBushelConversion(initForm.inventory.bushelsPerTonne * 0.907)
|
||||
}
|
||||
if (initForm.inventory.customGrain){
|
||||
setCustomGrain(initForm.inventory.customGrain)
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
||||
setBushelConversion(initForm.inventory.customGrain.bushelsPerTonne)
|
||||
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
setBushelConversion(initForm.inventory.customGrain.bushelsPerTonne * 0.907)
|
||||
}
|
||||
}
|
||||
//if the target temp is not set (older bins) make it the midpoint of the high and low temps assuming they are set otherwise make it 15
|
||||
if (!initForm.inventory.targetTemperature || initForm.inventory.targetTemperature) {
|
||||
|
|
@ -290,6 +302,21 @@ export default function BinSettings(props: Props) {
|
|||
? initForm.inventory.grainBushels * 35.239
|
||||
: 0
|
||||
: initForm.inventory?.grainBushels ?? 0;
|
||||
let weight = ""
|
||||
if(initForm.inventory){
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
||||
weight = (gb/initForm.inventory.bushelsPerTonne).toFixed(2)
|
||||
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
weight = (gb/(initForm.inventory.bushelsPerTonne * 0.907)).toFixed(2)
|
||||
}
|
||||
if(initForm.inventory.customGrain){
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
||||
weight = (gb/initForm.inventory.customGrain.bushelsPerTonne).toFixed(2)
|
||||
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
weight = (gb/(initForm.inventory.customGrain.bushelsPerTonne * 0.907)).toFixed(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setForm(initForm);
|
||||
setHighTempC(initForm.highTemp ?? 20);
|
||||
|
|
@ -299,9 +326,10 @@ export default function BinSettings(props: Props) {
|
|||
height: h,
|
||||
diameter: d,
|
||||
grainBushels: gb.toFixed(2),
|
||||
grainTonnes: initForm.inventory?.bushelsPerTonne
|
||||
? (gb / initForm.inventory.bushelsPerTonne).toFixed(2)
|
||||
: "",
|
||||
grainWeight: weight,
|
||||
// grainTons: initForm.inventory?.bushelsPerTon
|
||||
// ? (gb / initForm.inventory.bushelsPerTon).toFixed(2)
|
||||
// : "",
|
||||
grainType: initForm.inventory?.grainType
|
||||
? ToGrainOption(initForm.inventory.grainType)
|
||||
: null,
|
||||
|
|
@ -315,8 +343,7 @@ export default function BinSettings(props: Props) {
|
|||
tempTarget: target,
|
||||
hopperHeight: hopperHeight,
|
||||
topConeHeight: topconeHeight,
|
||||
sidewallHeight: sidewallHeight,
|
||||
bushelsPerTonne: initForm.inventory?.bushelsPerTonne.toString() ?? "0"
|
||||
sidewallHeight: sidewallHeight
|
||||
});
|
||||
setInMoistureString(initForm.inventory?.initialMoisture.toString() ?? "0");
|
||||
setTMoistureString(initForm.inventory?.targetMoisture.toString() ?? "0");
|
||||
|
|
@ -771,12 +798,103 @@ export default function BinSettings(props: Props) {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
const inventoryAmount = () => {
|
||||
const grainWeight = formExtension.grainWeight;
|
||||
const binQuantity = formExtension.grainBushels;
|
||||
|
||||
//as long as the storage type is not fertilizer it is some sort of grain, whether it is supported or custom is not important in this instance so we do not need to check for unknown
|
||||
if(storageType !== pond.BinStorage.BIN_STORAGE_FERTILIZER && ((getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) && bushelConversion !== 0 && grainWeight !== "")){
|
||||
return (
|
||||
<TextField
|
||||
label="Amount"
|
||||
value={grainWeight}
|
||||
type="number"
|
||||
onChange={event => {
|
||||
let newWeight = event.target.value;
|
||||
//get the bushel amount
|
||||
if (!isNaN(+newWeight)) {
|
||||
let newBushels = Math.round(+newWeight * bushelConversion * 100) / 100;
|
||||
updateForm(
|
||||
"inventory",
|
||||
pond.BinInventory.create({
|
||||
...form.inventory,
|
||||
grainBushels: newBushels
|
||||
})
|
||||
);
|
||||
//updateFormExtension("grainBushels", newBushels.toString())
|
||||
updateFormExtension("grainWeight", newWeight);
|
||||
}
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
disabled={!canEdit}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE ? "mT" : "t"}</InputAdornment>
|
||||
}}
|
||||
className={classes.bottomSpacing}
|
||||
/>
|
||||
)
|
||||
}else{
|
||||
return (
|
||||
<TextField
|
||||
label="Amount"
|
||||
value={binQuantity}
|
||||
type="number"
|
||||
onChange={event => {
|
||||
let capacity =
|
||||
bin?.settings.storage === pond.BinStorage.BIN_STORAGE_FERTILIZER
|
||||
? +inputCapacity * 35.239
|
||||
: inputCapacity;
|
||||
let value = event?.target.value;
|
||||
let v =
|
||||
value === ""
|
||||
? 0
|
||||
: isNaN(parseFloat(value))
|
||||
? 0
|
||||
: parseFloat(value) >= +capacity
|
||||
? +capacity
|
||||
: parseFloat(value) <= 0
|
||||
? 0
|
||||
: parseFloat(value);
|
||||
if (bin?.storage() === pond.BinStorage.BIN_STORAGE_FERTILIZER && v) {
|
||||
v = v / 35.239;
|
||||
}
|
||||
//setNewGrainQuantity(v);
|
||||
updateForm(
|
||||
"inventory",
|
||||
pond.BinInventory.create({
|
||||
...form.inventory,
|
||||
grainBushels: v
|
||||
})
|
||||
);
|
||||
updateFormExtension("grainBushels", value);
|
||||
}}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
{storageType === pond.BinStorage.BIN_STORAGE_FERTILIZER
|
||||
? "Litres"
|
||||
: "Bushels"}
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
disabled={
|
||||
!canEdit ||
|
||||
inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC
|
||||
}
|
||||
className={classes.bottomSpacing}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const inventoryForm = () => {
|
||||
const inventory = form.inventory;
|
||||
const empty = inventory ? inventory.empty : false;
|
||||
const bushPerTonne = formExtension.bushelsPerTonne;
|
||||
const binQuantity = formExtension.grainBushels;
|
||||
const grainTonnes = formExtension.grainTonnes;
|
||||
const grainType = formExtension.grainType;
|
||||
const grainSubtype = formExtension.grainSubtype;
|
||||
const grainUse = formExtension.grainUse;
|
||||
|
|
@ -805,11 +923,11 @@ export default function BinSettings(props: Props) {
|
|||
pond.BinInventory.create({
|
||||
...form.inventory,
|
||||
grainType: pond.Grain.GRAIN_CUSTOM,
|
||||
bushelsPerTonne: 0
|
||||
bushelsPerTonne: 0,
|
||||
})
|
||||
);
|
||||
updateFormExtension("grainType", null);
|
||||
updateFormExtension("bushelsPerTonne", "");
|
||||
setBushelConversion(0)
|
||||
} else {
|
||||
setStorageType(pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN);
|
||||
updateForm(
|
||||
|
|
@ -859,7 +977,8 @@ export default function BinSettings(props: Props) {
|
|||
let storageType = parseFloat(value) as pond.BinStorage;
|
||||
setStorageType(storageType);
|
||||
if (storageType === pond.BinStorage.BIN_STORAGE_FERTILIZER) {
|
||||
formExtension.bushelsPerTonne = "";
|
||||
// formExtension.bushelsPerTonne = "";
|
||||
setBushelConversion(0)
|
||||
formExtension.grainBushels = (
|
||||
parseFloat(formExtension.grainBushels) * 35.239
|
||||
).toFixed(0);
|
||||
|
|
@ -1054,7 +1173,29 @@ export default function BinSettings(props: Props) {
|
|||
</React.Fragment>
|
||||
:
|
||||
<Box sx={{border: "1px solid white", borderRadius: 2, padding: 2, marginBottom: 2}}>
|
||||
<CustomGrainSelector initialGrain={customGrain} onGrainSettingsChange={(newGrainSettings) => {setCustomGrain(newGrainSettings)}}/>
|
||||
<CustomGrainSelector
|
||||
initialGrain={customGrain}
|
||||
onGrainSettingsChange={(newGrainSettings) => {
|
||||
setCustomGrain(newGrainSettings)
|
||||
let conversion = 0
|
||||
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
||||
conversion = newGrainSettings.bushelsPerTonne
|
||||
}else if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
conversion = newGrainSettings.bushelsPerTonne * 0.907
|
||||
}
|
||||
updateForm(
|
||||
"inventory",
|
||||
pond.BinInventory.create({
|
||||
...form.inventory,
|
||||
bushelsPerTonne: newGrainSettings.bushelsPerTonne,
|
||||
})
|
||||
);
|
||||
setBushelConversion(conversion)
|
||||
if(conversion !== 0 && !isNaN(+binQuantity)){
|
||||
updateFormExtension("grainWeight", (+binQuantity/conversion).toFixed(2))
|
||||
}
|
||||
}}/>
|
||||
</Box>
|
||||
}
|
||||
</React.Fragment>
|
||||
|
|
@ -1073,11 +1214,21 @@ export default function BinSettings(props: Props) {
|
|||
pond.BinInventory.create({
|
||||
...form.inventory,
|
||||
grainType: newGrainType,
|
||||
bushelsPerTonne: GrainDescriber(newGrainType).bushelsPerTonne
|
||||
bushelsPerTonne: GrainDescriber(newGrainType).bushelsPerTonne,
|
||||
})
|
||||
);
|
||||
//doesn't need to update the form extension to display the bushels per tonne as it is not changeable for the supported grain
|
||||
//and is reset whenever the user switches between custom or not
|
||||
let conversion = 0
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
||||
conversion = GrainDescriber(newGrainType).bushelsPerTonne
|
||||
}else if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
conversion = GrainDescriber(newGrainType).bushelsPerTon
|
||||
}
|
||||
setBushelConversion(conversion)
|
||||
if(conversion !== 0 && !isNaN(+binQuantity)){
|
||||
//updateFormExtension("grainWeight", (+binQuantity/conversion).toFixed(2))
|
||||
let newWeight = (+binQuantity/conversion).toFixed(2)
|
||||
formExtension.grainWeight = newWeight
|
||||
}
|
||||
updateFormExtension("grainType", option);
|
||||
}}
|
||||
group
|
||||
|
|
@ -1106,7 +1257,8 @@ export default function BinSettings(props: Props) {
|
|||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_BUSHELS ||
|
||||
{inventoryAmount()}
|
||||
{/* {getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_BUSHELS ||
|
||||
storageType === pond.BinStorage.BIN_STORAGE_FERTILIZER ||
|
||||
formExtension.bushelsPerTonne === "0" ||
|
||||
formExtension.bushelsPerTonne === "" ? (
|
||||
|
|
@ -1163,7 +1315,7 @@ export default function BinSettings(props: Props) {
|
|||
) : (
|
||||
<TextField
|
||||
label="Amount"
|
||||
value={grainTonnes}
|
||||
value={grainWeight}
|
||||
type="number"
|
||||
onChange={event => {
|
||||
let newWeight = event.target.value;
|
||||
|
|
@ -1180,7 +1332,7 @@ export default function BinSettings(props: Props) {
|
|||
})
|
||||
);
|
||||
//updateFormExtension("grainBushels", newBushels.toString())
|
||||
updateFormExtension("grainTonnes", newWeight);
|
||||
updateFormExtension("grainWeight", newWeight);
|
||||
}
|
||||
}}
|
||||
fullWidth
|
||||
|
|
@ -1191,7 +1343,7 @@ export default function BinSettings(props: Props) {
|
|||
}}
|
||||
className={classes.bottomSpacing}
|
||||
/>
|
||||
)}
|
||||
)} */}
|
||||
{!isCustomInventory && (
|
||||
<SearchSelect
|
||||
label="Use"
|
||||
|
|
|
|||
|
|
@ -196,14 +196,27 @@ export default function BinTransactions(props: Props){
|
|||
}
|
||||
}
|
||||
|
||||
const grainQuantityDisplay = (bushels: number) => {
|
||||
const grainTransaction = selectedTransaction?.transaction.transaction?.grainTransaction ?? pond.GrainTransaction.create()
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainTransaction.bushelsPerTonne > 1){
|
||||
let tonneWeight = bushels / grainTransaction.bushelsPerTonne
|
||||
return tonneWeight + " mT"
|
||||
}
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainTransaction.bushelsPerTonne > 1){
|
||||
let tonneWeight = bushels / (grainTransaction.bushelsPerTonne * 0.907)
|
||||
return tonneWeight + " t"
|
||||
}
|
||||
return bushels + " bushels"
|
||||
}
|
||||
|
||||
// dialog for approving a transaction
|
||||
const approvalDialog = () => {
|
||||
// get the display information for the transaction
|
||||
const grainTransaction = selectedTransaction?.transaction.transaction?.grainTransaction ?? pond.GrainTransaction.create()
|
||||
const bushels = grainTransaction.bushels ?? 0
|
||||
const weight = bushels / grainTransaction.bushelsPerTonne
|
||||
//const weight = bushels / grainTransaction.bushelsPerTonne
|
||||
let graintype = grainTransaction.grainType ?? pond.Grain.GRAIN_INVALID
|
||||
const grainDisplay = graintype === pond.Grain.GRAIN_CUSTOM
|
||||
const grainTypeDisplay = graintype === pond.Grain.GRAIN_CUSTOM
|
||||
? grainTransaction.customGrain
|
||||
: GrainDescriber(grainTransaction.grainType).name
|
||||
|
||||
|
|
@ -215,7 +228,7 @@ export default function BinTransactions(props: Props){
|
|||
<DialogTitle>Accept Transaction</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box>
|
||||
<Typography>{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? weight + " mT" : bushels + " bushels"} of {grainTransaction.subtype} {grainDisplay} moved</Typography>
|
||||
<Typography>{grainQuantityDisplay(bushels)} of {grainTransaction.subtype} {grainTypeDisplay} moved</Typography>
|
||||
<Typography margin={1} fontWeight={650}>From:</Typography>
|
||||
{/*Object type selection*/}
|
||||
<Autocomplete
|
||||
|
|
@ -378,9 +391,9 @@ export default function BinTransactions(props: Props){
|
|||
// get the display information for the transaction
|
||||
const grainTransaction = t.transaction.transaction?.grainTransaction ?? pond.GrainTransaction.create()
|
||||
const bushels = grainTransaction.bushels ?? 0
|
||||
const weight = bushels / grainTransaction.bushelsPerTonne
|
||||
// const weight = bushels / grainTransaction.bushelsPerTonne
|
||||
let graintype = grainTransaction.grainType ?? pond.Grain.GRAIN_INVALID
|
||||
const grainDisplay = graintype === pond.Grain.GRAIN_CUSTOM
|
||||
const grainTypeDisplay = graintype === pond.Grain.GRAIN_CUSTOM
|
||||
? grainTransaction.customGrain
|
||||
: GrainDescriber(grainTransaction.grainType).name
|
||||
return (
|
||||
|
|
@ -394,7 +407,7 @@ export default function BinTransactions(props: Props){
|
|||
</Box>
|
||||
</Box>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Typography sx={{margin: "auto", marginLeft: 0}}>{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? weight + " mT" : bushels + " bushels"} of {grainTransaction.subtype} {grainDisplay} moved</Typography>
|
||||
<Typography sx={{margin: "auto", marginLeft: 0}}>{grainQuantityDisplay(bushels)} of {grainTransaction.subtype} {grainTypeDisplay} moved</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={selectedMatch?.transaction.key === t.transaction.key}
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ export default function BinVisualizer(props: Props) {
|
|||
const [storageType, setStorageType] = useState<pond.BinStorage>(
|
||||
pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN
|
||||
);
|
||||
const [bushPerTonne, setBushPerTonne] = useState("0");
|
||||
//const [bushPerTonne, setBushPerTonne] = useState("0");
|
||||
const [grainSubtype, setGrainSubtype] = useState("");
|
||||
|
||||
const [loadingTrend, setLoadingTrend] = useState(false);
|
||||
|
|
@ -326,7 +326,7 @@ export default function BinVisualizer(props: Props) {
|
|||
setCustomTypeName(bin.settings.inventory.customTypeName);
|
||||
setGrainType(bin.settings.inventory.grainType);
|
||||
setGrainOption(ToGrainOption(bin.settings.inventory.grainType));
|
||||
setBushPerTonne(bin.settings.inventory.bushelsPerTonne.toFixed(2));
|
||||
//setBushPerTonne(bin.settings.inventory.bushelsPerTonne.toFixed(2));
|
||||
setGrainSubtype(bin.subtype());
|
||||
setNewBinMode(bin.settings.mode);
|
||||
setNewGrainSettings(bin.customGrain());
|
||||
|
|
@ -495,8 +495,10 @@ export default function BinVisualizer(props: Props) {
|
|||
if (bin.storage() === pond.BinStorage.BIN_STORAGE_FERTILIZER) {
|
||||
return Math.round(val * 35.239 * 100) / 100;
|
||||
} else {
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && bin.bushelsPerTonne() > 1) {
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
|
||||
return Math.round((val / bin.bushelsPerTonne()) * 100) / 100;
|
||||
} else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
|
||||
return Math.round((val / (bin.bushelsPerTonne()*0.907)) * 100) / 100;
|
||||
}
|
||||
}
|
||||
return val;
|
||||
|
|
@ -511,17 +513,19 @@ export default function BinVisualizer(props: Props) {
|
|||
if (bin.storage() === pond.BinStorage.BIN_STORAGE_FERTILIZER) {
|
||||
return " / " + capacity.toLocaleString() + " L";
|
||||
}
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && bin.bushelsPerTonne() > 1) {
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
|
||||
return "mT (" + bin.fillPercent() + "%)";
|
||||
} else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
|
||||
return "t (" + bin.fillPercent() + "%)";
|
||||
} else {
|
||||
return " / " + capacity.toLocaleString() + " bu";
|
||||
}
|
||||
};
|
||||
|
||||
const grainErrorCheck = () => {
|
||||
if (isNaN(parseFloat(bushPerTonne))) return true;
|
||||
return false;
|
||||
};
|
||||
// const grainErrorCheck = () => {
|
||||
// if (isNaN(parseFloat(bushPerTonne))) return true;
|
||||
// return false;
|
||||
// };
|
||||
|
||||
const inventoryOverview = () => {
|
||||
const capacity = bin.settings.specs?.bushelCapacity ?? 0;
|
||||
|
|
@ -774,7 +778,7 @@ export default function BinVisualizer(props: Props) {
|
|||
setCustomTypeName(bin.settings.inventory.customTypeName);
|
||||
setGrainType(bin.settings.inventory.grainType);
|
||||
setGrainOption(ToGrainOption(bin.settings.inventory.grainType));
|
||||
setBushPerTonne(bin.settings.inventory.bushelsPerTonne.toFixed(2));
|
||||
//setBushPerTonne(bin.settings.inventory.bushelsPerTonne.toFixed(2));
|
||||
setNewGrainSettings(bin.customGrain())
|
||||
setGrainSubtype(bin.subtype());
|
||||
}
|
||||
|
|
@ -802,7 +806,7 @@ export default function BinVisualizer(props: Props) {
|
|||
checked={isCustomInventory}
|
||||
onChange={(_, checked) => {
|
||||
setIsCustomInventory(checked);
|
||||
setBushPerTonne("0");
|
||||
//setBushPerTonne("0");
|
||||
setGrainSubtype("");
|
||||
setCustomTypeName("");
|
||||
setGrainOption(ToGrainOption(pond.Grain.GRAIN_NONE));
|
||||
|
|
@ -834,7 +838,7 @@ export default function BinVisualizer(props: Props) {
|
|||
: pond.Grain.GRAIN_INVALID;
|
||||
setGrainOption(ToGrainOption(newGrainType));
|
||||
setGrainType(newGrainType);
|
||||
setBushPerTonne(GrainDescriber(newGrainType).bushelsPerTonne.toFixed(2));
|
||||
//setBushPerTonne(GrainDescriber(newGrainType).bushelsPerTonne.toFixed(2));
|
||||
}}
|
||||
group
|
||||
disabled={!canEdit}
|
||||
|
|
@ -897,7 +901,7 @@ export default function BinVisualizer(props: Props) {
|
|||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={grainErrorCheck()}
|
||||
// disabled={grainErrorCheck()}
|
||||
onClick={() => {
|
||||
updateBin();
|
||||
setGrainChangeDialog(false);
|
||||
|
|
@ -1321,13 +1325,20 @@ export default function BinVisualizer(props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && bin.bushelsPerTonne() > 1) {
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
|
||||
diffDisplay = diffDisplay / bin.bushelsPerTonne();
|
||||
if (pendingDisplay) {
|
||||
pendingDisplay = pendingDisplay / bin.bushelsPerTonne();
|
||||
}
|
||||
}
|
||||
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
|
||||
diffDisplay = diffDisplay / (bin.bushelsPerTonne()*0.907);
|
||||
if (pendingDisplay) {
|
||||
pendingDisplay = pendingDisplay / (bin.bushelsPerTonne()*0.907);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box display="flex" width={1} justifyContent="flex-end">
|
||||
<FullScreen handle={fullScreenHandler}>
|
||||
|
|
@ -1987,9 +1998,10 @@ export default function BinVisualizer(props: Props) {
|
|||
//update all the grain stuff
|
||||
b.settings.inventory.grainType = grainType;
|
||||
b.settings.inventory.customTypeName = customTypeName;
|
||||
b.settings.inventory.bushelsPerTonne = isNaN(parseFloat(bushPerTonne))
|
||||
? 0
|
||||
: parseFloat(bushPerTonne);
|
||||
b.settings.inventory.bushelsPerTonne = GrainDescriber(grainType).bushelsPerTonne
|
||||
// b.settings.inventory.bushelsPerTonne = isNaN(parseFloat(bushPerTonne))
|
||||
// ? 0
|
||||
// : parseFloat(bushPerTonne);
|
||||
b.settings.inventory.grainSubtype = grainSubtype;
|
||||
b.settings.inventory.customGrain = newGrainSettings
|
||||
}
|
||||
|
|
@ -2010,7 +2022,6 @@ export default function BinVisualizer(props: Props) {
|
|||
}
|
||||
b.settings.mode = newBinMode;
|
||||
}
|
||||
console.log(b.settings)
|
||||
binAPI.updateBin(bin.key(), b.settings, as).then(resp => {
|
||||
refresh();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -652,9 +652,47 @@ export default function BinyardDisplay(props: Props) {
|
|||
);
|
||||
};
|
||||
|
||||
const grainUnitDisplay = (custom?: pond.CustomInventory) => {
|
||||
// If custom and not convertible, always bushels
|
||||
if (custom && custom.bushelsPerTonne <= 1) {
|
||||
return " bu";
|
||||
}
|
||||
|
||||
switch (getGrainUnit()) {
|
||||
case pond.GrainUnit.GRAIN_UNIT_TONNE:
|
||||
return " mT";
|
||||
case pond.GrainUnit.GRAIN_UNIT_TON:
|
||||
return " t";
|
||||
default:
|
||||
return " bu";
|
||||
}
|
||||
};
|
||||
|
||||
const customQuantityDisplay = (customInventory: pond.CustomInventory, bushels: number) => {
|
||||
let amount = bushels
|
||||
if(customInventory.bushelsPerTonne > 1){
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
||||
amount = bushels/customInventory.bushelsPerTonne
|
||||
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
amount = bushels/(customInventory.bushelsPerTonne*0.907)
|
||||
}
|
||||
}
|
||||
return Math.round(amount*100)/100
|
||||
}
|
||||
|
||||
const supportedGrainDisplay = (grain: pond.Grain, bushels: number) => {
|
||||
const describer = GrainDescriber(grain)
|
||||
let amount = bushels
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
||||
amount = bushels/describer.bushelsPerTonne
|
||||
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
amount = bushels/describer.bushelsPerTon
|
||||
}
|
||||
return Math.round(amount*100)/100
|
||||
}
|
||||
|
||||
const binUtilizationList = () => {
|
||||
const hasInventory = binMetrics && binMetrics.grainInventory.length > 0;
|
||||
const useWeight = getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT;
|
||||
return (
|
||||
<Accordion
|
||||
className={classes.accordion}
|
||||
|
|
@ -695,17 +733,9 @@ export default function BinyardDisplay(props: Props) {
|
|||
<GridListTile key={key}>
|
||||
<BinUtilizationChart
|
||||
grain={inv.grainType}
|
||||
customUnit={useWeight ? " mT" : " bu"}
|
||||
bushelAmount={
|
||||
useWeight
|
||||
? inv.bushelAmount / GrainDescriber(inv.grainType).bushelsPerTonne
|
||||
: inv.bushelAmount
|
||||
}
|
||||
bushelCapacity={
|
||||
useWeight
|
||||
? inv.bushelCapacity / GrainDescriber(inv.grainType).bushelsPerTonne
|
||||
: inv.bushelCapacity
|
||||
}
|
||||
customUnit={grainUnitDisplay()}
|
||||
bushelAmount={supportedGrainDisplay(inv.grainType, inv.bushelAmount)}
|
||||
bushelCapacity={supportedGrainDisplay(inv.grainType, inv.bushelCapacity)}
|
||||
onClick={() => {
|
||||
setContentFilter(pond.Grain[inv.grainType]);
|
||||
loadMoreBins(pond.Grain[inv.grainType]);
|
||||
|
|
@ -724,11 +754,10 @@ export default function BinyardDisplay(props: Props) {
|
|||
amount = amount * 35.239;
|
||||
cap = cap * 35.239;
|
||||
unit = " L";
|
||||
}
|
||||
if (useWeight && inv.bushelsPerTonne > 1) {
|
||||
amount = amount / inv.bushelsPerTonne;
|
||||
cap = cap / inv.bushelsPerTonne;
|
||||
unit = " mT";
|
||||
}else{
|
||||
amount = customQuantityDisplay(inv, amount)
|
||||
cap = customQuantityDisplay(inv, cap)
|
||||
unit = grainUnitDisplay(inv)
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -69,9 +69,12 @@ export default function BinLevelOverTime(props: Props) {
|
|||
if (fertilizerBin && cap) {
|
||||
cap = cap * 35.239;
|
||||
}
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && cap) {
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && cap) {
|
||||
cap = cap / bin.bushelsPerTonne();
|
||||
}
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && cap) {
|
||||
cap = cap / (bin.bushelsPerTonne()*0.907);
|
||||
}
|
||||
setCapacity(cap);
|
||||
binAPI
|
||||
.listHistoryBetween(bin.key(), 500, startDate.toISOString(), endDate.toISOString())
|
||||
|
|
@ -85,13 +88,18 @@ export default function BinLevelOverTime(props: Props) {
|
|||
if (hist.settings?.inventory) {
|
||||
let bushels = hist.settings.inventory.grainBushels ?? 0;
|
||||
if (bushels !== lastBushels) {
|
||||
let grainDisplay = bushels
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
|
||||
grainDisplay = Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100;
|
||||
}
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
|
||||
grainDisplay = Math.round((bushels / (bin.bushelsPerTonne()*0.907)) * 100) / 100;
|
||||
}
|
||||
let newData: InventoryAt = {
|
||||
timestamp: moment(hist.timestamp).valueOf(),
|
||||
value: fertilizerBin
|
||||
? Math.round(bushels * 35.239)
|
||||
: getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT
|
||||
? Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100
|
||||
: bushels
|
||||
: grainDisplay
|
||||
};
|
||||
data.push(newData);
|
||||
lastBushels = bushels;
|
||||
|
|
@ -112,12 +120,17 @@ export default function BinLevelOverTime(props: Props) {
|
|||
let currentTime = moment().valueOf();
|
||||
if (data.length === 0) {
|
||||
let bushels = bin.bushels();
|
||||
let grainDisplay = bushels
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
|
||||
grainDisplay = Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100;
|
||||
}
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
|
||||
grainDisplay = Math.round((bushels / (bin.bushelsPerTonne()*0.907)) * 100) / 100;
|
||||
}
|
||||
data.push({
|
||||
value: fertilizerBin
|
||||
? Math.round(bushels * 35.239)
|
||||
: getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT
|
||||
? Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100
|
||||
: bushels,
|
||||
: grainDisplay,
|
||||
timestamp: currentTime
|
||||
});
|
||||
}
|
||||
|
|
@ -143,9 +156,12 @@ export default function BinLevelOverTime(props: Props) {
|
|||
if (fertilizerBin && cap) {
|
||||
cap = cap * 35.239;
|
||||
}
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && cap) {
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && cap) {
|
||||
cap = cap / bin.bushelsPerTonne();
|
||||
}
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && cap) {
|
||||
cap = cap / (bin.bushelsPerTonne()*0.907);
|
||||
}
|
||||
setCapacity(cap);
|
||||
let autoBarData: BarData[] = [];
|
||||
|
||||
|
|
@ -163,8 +179,15 @@ export default function BinLevelOverTime(props: Props) {
|
|||
m.values.forEach((val, i) => {
|
||||
if (val.values[0] && m.timestamps[i]) {
|
||||
if (lastBushels !== val.values[0]) {
|
||||
let grainDisplay = val.values[0]
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
|
||||
grainDisplay = Math.round((grainDisplay / bin.bushelsPerTonne()) * 100) / 100;
|
||||
}
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
|
||||
grainDisplay = Math.round((grainDisplay / (bin.bushelsPerTonne()*0.907)) * 100) / 100;
|
||||
}
|
||||
autoBarData.push({
|
||||
value: val.values[0],
|
||||
value: grainDisplay,
|
||||
timestamp: moment(m.timestamps[i]).valueOf(),
|
||||
});
|
||||
lastBushels = val.values[0];
|
||||
|
|
@ -175,12 +198,17 @@ export default function BinLevelOverTime(props: Props) {
|
|||
if (autoBarData.length === 0) {
|
||||
let bushels = bin.bushels();
|
||||
let currentTime = moment().valueOf();
|
||||
let grainDisplay = bushels
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
|
||||
grainDisplay = Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100;
|
||||
}
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
|
||||
grainDisplay = Math.round((bushels / (bin.bushelsPerTonne()*0.907)) * 100) / 100;
|
||||
}
|
||||
autoBarData.push({
|
||||
value: fertilizerBin
|
||||
? Math.round(bushels * 35.239)
|
||||
: getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT
|
||||
? Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100
|
||||
: bushels,
|
||||
: grainDisplay,
|
||||
timestamp: currentTime
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { ChevronRight } from "@mui/icons-material";
|
||||
import { Avatar, Box, Divider, Drawer, IconButton, Theme, Tooltip, Typography } from "@mui/material";
|
||||
import { Avatar, Box, Drawer, IconButton, Theme, Tooltip, Typography } from "@mui/material";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { useMobile } from "hooks";
|
||||
import { Team } from "models";
|
||||
|
|
@ -7,7 +7,7 @@ import Chat from "./Chat";
|
|||
import { pond } from "protobuf-ts/pond";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTeamAPI } from "providers";
|
||||
import { openCrispChat } from './CrispChat';
|
||||
import { closeCrispChat, openCrispChat } from './CrispChat';
|
||||
import RobotIcon from "products/CommonIcons/robotIcon";
|
||||
|
||||
const useStyles = makeStyles<Theme>((theme: Theme) => ({
|
||||
|
|
@ -67,6 +67,12 @@ export function ChatDrawer(props: Props) {
|
|||
onClose()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
closeCrispChat()
|
||||
}
|
||||
}, [open])
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
open={open}
|
||||
|
|
|
|||
|
|
@ -92,9 +92,14 @@ export default function ContractSettings(props: Props) {
|
|||
setContractValue(contract.settings.totalValue.toString());
|
||||
setCommodity(contract.settings.commodity);
|
||||
setContractDate(contract.settings.contractDate);
|
||||
setConversionValue(
|
||||
contract.settings.conversionValue > 0 ? contract.settings.conversionValue.toString() : "1"
|
||||
);
|
||||
let conv = 1
|
||||
if(contract.settings.conversionValue > 1){
|
||||
conv = contract.settings.conversionValue
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
conv = conv * 0.907
|
||||
}
|
||||
}
|
||||
setConversionValue(conv.toFixed(2));
|
||||
setUseCustom(contract.isCustom());
|
||||
}
|
||||
}, [contract]);
|
||||
|
|
@ -102,12 +107,21 @@ export default function ContractSettings(props: Props) {
|
|||
useEffect(() => {
|
||||
switch (contractType) {
|
||||
case pond.ContractType.CONTRACT_TYPE_GRAIN:
|
||||
let conversionLabel = "Bushels Per Tonne"
|
||||
let adornment = "bu"
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
||||
adornment = "mT"
|
||||
}
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
conversionLabel = "Bushels Per Ton"
|
||||
adornment = "t"
|
||||
}
|
||||
setLabels({
|
||||
sizeLabel: "Total Grain on Contract",
|
||||
commodityLabel: "Select Grain Type",
|
||||
conversionLabel: "Bushels Per Tonne",
|
||||
conversionLabel: conversionLabel,
|
||||
deliveryLabel: "Grain Delivered",
|
||||
adornment: getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? "mT" : "bu"
|
||||
adornment: adornment
|
||||
});
|
||||
break;
|
||||
default:
|
||||
|
|
@ -148,13 +162,22 @@ export default function ContractSettings(props: Props) {
|
|||
};
|
||||
|
||||
const submit = () => {
|
||||
const conVal = isNaN(parseFloat(conversionValue)) ? 1 : parseFloat(conversionValue);
|
||||
let conVal = isNaN(parseFloat(conversionValue)) ? 1 : parseFloat(conversionValue);
|
||||
//the toStoredUnit will convert what they entered for amount and size using the conversion that they entered
|
||||
//we will assume that both of those entries are using the same unit ie both US ton or both metric tonne
|
||||
const deliveredVal = isNaN(parseFloat(deliveredAmount))
|
||||
? 0
|
||||
: Math.round(Contract.toStoredUnit(parseFloat(deliveredAmount), contractType, conVal));
|
||||
const sizeVal = isNaN(parseFloat(contractSize))
|
||||
? 0
|
||||
: Math.round(Contract.toStoredUnit(parseFloat(contractSize), contractType, conVal));
|
||||
|
||||
//then after the conversion to bushels change the conversion value to metric to be stored in the contract
|
||||
if(contractType === pond.ContractType.CONTRACT_TYPE_GRAIN){
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
conVal = conVal / 0.907 //convert the ton they entered to mT to be stored in the contracts conversion value
|
||||
}
|
||||
}
|
||||
if (contract) {
|
||||
let settings: pond.ContractSettings = contract.settings;
|
||||
settings.commodity = commodity;
|
||||
|
|
@ -201,7 +224,6 @@ export default function ContractSettings(props: Props) {
|
|||
settings.type = contractType;
|
||||
settings.conversionValue = conVal;
|
||||
settings.contractDate = contractDate;
|
||||
|
||||
contractAPI
|
||||
.addContract(name, settings, as)
|
||||
.then(resp => {
|
||||
|
|
@ -490,7 +512,9 @@ export default function ContractSettings(props: Props) {
|
|||
InputLabelProps={{ shrink: true }}
|
||||
error={isNaN(parseFloat(conversionValue))}
|
||||
helperText={isNaN(parseFloat(conversionValue)) ? "Must be a valid number" : ""}
|
||||
onChange={e => setConversionValue(e.target.value)}
|
||||
onChange={e => {
|
||||
setConversionValue(e.target.value)
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -27,11 +27,17 @@ export default function ContractTransactionGraph(props: Props) {
|
|||
if (transaction?.grainTransaction) {
|
||||
value = transaction.grainTransaction.bushels;
|
||||
if (
|
||||
getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT &&
|
||||
getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE &&
|
||||
transaction.grainTransaction.bushelsPerTonne > 1
|
||||
) {
|
||||
value = value / transaction.grainTransaction.bushelsPerTonne;
|
||||
}
|
||||
if (
|
||||
getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON &&
|
||||
transaction.grainTransaction.bushelsPerTonne > 1
|
||||
) {
|
||||
value = value / (transaction.grainTransaction.bushelsPerTonne * 0.907);
|
||||
}
|
||||
}
|
||||
d.push({
|
||||
timestamp: time.valueOf(),
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import React, { useCallback, useEffect, useState } from "react";
|
|||
//import { Redirect } from "react-router";
|
||||
import { or } from "utils/types";
|
||||
import DeviceActions from "./DeviceActions";
|
||||
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
title: {
|
||||
|
|
@ -97,45 +98,28 @@ export default function Device(props: Props) {
|
|||
const load = useCallback(() => {
|
||||
if (user.id() === "") return;
|
||||
setIsLoading(true);
|
||||
//let devicePromise = deviceAPI.get(deviceID);
|
||||
let userPromise = userAPI.getUser(user.id(), deviceScope(deviceID.toString()));
|
||||
let componentsPromise = componentAPI.list(
|
||||
deviceID,
|
||||
undefined,
|
||||
[deviceID.toString()],
|
||||
["device"],
|
||||
true,
|
||||
as
|
||||
);
|
||||
let interactionsPromise = interactionsAPI.listInteractionsByDevice(deviceID, undefined, as);
|
||||
let groupPromise: Promise<any> = Promise.resolve(undefined);
|
||||
Promise.all([userPromise, componentsPromise, interactionsPromise, groupPromise])
|
||||
.then(([userRes, componentsRes, interactionsRes]) => {
|
||||
//let rDevice = DeviceModel.any(deviceRes.data);
|
||||
let rawComponents: Array<any> = or(componentsRes.data.components, []);
|
||||
let rComponents: Map<string, Component> = new Map();
|
||||
rawComponents.forEach((rawComponent: any) => {
|
||||
let component = Component.any(rawComponent);
|
||||
rComponents.set(component.key(), component);
|
||||
});
|
||||
deviceAPI.getPageData(deviceID, getContextKeys(), getContextTypes(), as).then(resp => {
|
||||
console.log(resp)
|
||||
let rawComponents: Array<any> = or(resp.data.components, []);
|
||||
let rComponents: Map<string, Component> = new Map();
|
||||
rawComponents.forEach((rawComponent: any) => {
|
||||
let component = Component.any(rawComponent);
|
||||
rComponents.set(component.key(), component);
|
||||
});
|
||||
setComponents(rComponents);
|
||||
|
||||
setComponents(rComponents);
|
||||
|
||||
setInteractions(interactionsRes);
|
||||
setPermissions(userRes.permissions);
|
||||
setPreferences(userRes.preferences);
|
||||
|
||||
setDevice(props.device);
|
||||
setLongitude(props.device.status.longitude ? props.device.status.longitude : NaN);
|
||||
setLatitude(props.device.status.latitude ? props.device.status.latitude : NaN);
|
||||
let available = FindAvailablePositions(
|
||||
Array.from(rComponents.values()),
|
||||
props.device.settings.product
|
||||
);
|
||||
setAvailablePositions(available.GetAvailability());
|
||||
setAvailableOffsets(available.offsetAvailability);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
setInteractions(resp.data.interactions.map(i => Interaction.create(i)))
|
||||
setPermissions(resp.data.permissions)
|
||||
setDevice(props.device);
|
||||
setLongitude(props.device.status.longitude ? props.device.status.longitude : NaN);
|
||||
setLatitude(props.device.status.latitude ? props.device.status.latitude : NaN);
|
||||
let available = FindAvailablePositions(
|
||||
Array.from(rComponents.values()),
|
||||
props.device.settings.product
|
||||
);
|
||||
setAvailablePositions(available.GetAvailability());
|
||||
setAvailableOffsets(available.offsetAvailability);
|
||||
}).catch(err => {
|
||||
setDevice(DeviceModel.create());
|
||||
setComponents(new Map());
|
||||
setInteractions([]);
|
||||
|
|
@ -145,8 +129,9 @@ export default function Device(props: Props) {
|
|||
setPreferences(pond.UserPreferences.create());
|
||||
setInvalidDevice(true);
|
||||
error(err);
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
}).finally(() => {
|
||||
setIsLoading(false)
|
||||
})
|
||||
usageAPI
|
||||
.getUsage(deviceID, moment().subtract(1, "days"))
|
||||
.then((res: any) => {
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ export default function FieldSettings(props: Props) {
|
|||
settings.grainSubtype = grainSubtype;
|
||||
settings.landLocation = landLocation;
|
||||
settings.acres = acres;
|
||||
settings.bushelsPerTonne = isNaN(parseFloat(bushelsPerTonne)) ? 1 : parseFloat(bushelsPerTonne);
|
||||
|
||||
fieldAPI
|
||||
.updateField(selectedField.key(), settings, undefined, as)
|
||||
|
|
@ -121,6 +122,7 @@ export default function FieldSettings(props: Props) {
|
|||
geo.shapes = borders;
|
||||
}
|
||||
settings.fieldGeoData = geo;
|
||||
settings.bushelsPerTonne = isNaN(parseFloat(bushelsPerTonne)) ? 1 : parseFloat(bushelsPerTonne);
|
||||
fieldAPI
|
||||
.addField(settings, as)
|
||||
.then(resp => {
|
||||
|
|
@ -226,6 +228,7 @@ export default function FieldSettings(props: Props) {
|
|||
if (option) {
|
||||
let grainType = pond.Grain[option.value as keyof typeof pond.Grain];
|
||||
setCropType(grainType);
|
||||
console.log("set bpt here")
|
||||
setBushelsPerTonne(GrainDescriber(grainType).bushelsPerTonne.toString());
|
||||
}
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Box, Card, CardHeader, Grid2, Typography } from "@mui/material"
|
||||
import { Card, CardHeader, Grid2, Typography } from "@mui/material"
|
||||
import { blue, orange } from "@mui/material/colors"
|
||||
import SingleSetAreaChart, { SSAreaDataPoint } from "charts/SingleSetAreaChart"
|
||||
import { useComponentAPI } from "hooks"
|
||||
|
|
@ -38,11 +38,68 @@ export default function GateDeltaTempGraph(props: Props){
|
|||
const warmingColour = orange["500"]
|
||||
const coolingColour = blue["500"]
|
||||
|
||||
const getUnitByType = (unitMeasurements: UnitMeasurement[], type: quack.MeasurementType) => {
|
||||
return unitMeasurements.find(u => u.type === type);
|
||||
}
|
||||
|
||||
const buildDeltas = (ambientMeasurements: UnitMeasurement[], tempCompMeasurements: UnitMeasurement[], measurementType: quack.MeasurementType) => {
|
||||
const ambient = getUnitByType(ambientMeasurements, measurementType);
|
||||
const outlet = getUnitByType(tempCompMeasurements, measurementType);
|
||||
|
||||
if (!ambient || !outlet) return [];
|
||||
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
|
||||
let lastAmbient: number | null = null;
|
||||
let lastOutlet: number | null = null;
|
||||
|
||||
const deltas: SSAreaDataPoint[] = [];
|
||||
|
||||
const ambientTimes = ambient.timestamps.map(t => moment(t).valueOf()).reverse();
|
||||
const outletTimes = outlet.timestamps.map(t => moment(t).valueOf()).reverse();
|
||||
const ambientValues = [...ambient.values].reverse();
|
||||
const outletValues = [...outlet.values].reverse();
|
||||
|
||||
while (i < ambientTimes.length || j < outletTimes.length) {
|
||||
|
||||
const aTime = i < ambientTimes.length ? ambientTimes[i] : Infinity;
|
||||
const bTime = j < outletTimes.length ? outletTimes[j] : Infinity;
|
||||
|
||||
if (aTime <= bTime) {
|
||||
const valueArray = ambientValues[i++];
|
||||
if (!valueArray.error && valueArray.values.length > 0) {
|
||||
lastAmbient = valueArray.values[valueArray.values.length -1];
|
||||
}
|
||||
|
||||
if (lastAmbient !== null && lastOutlet !== null) {
|
||||
deltas.push({
|
||||
timestamp: aTime,
|
||||
value: Math.round((lastOutlet - lastAmbient)*100)/100
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
const valueArray = outletValues[j++];
|
||||
if (!valueArray.error && valueArray.values.length > 0) {
|
||||
lastOutlet = valueArray.values[valueArray.values.length -1];
|
||||
}
|
||||
|
||||
if (lastAmbient !== null && lastOutlet !== null) {
|
||||
deltas.push({
|
||||
timestamp: bTime,
|
||||
value: Math.round((lastOutlet - lastAmbient)*100)/100
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return deltas
|
||||
}
|
||||
|
||||
/**
|
||||
* the use effect will use the temp component passed in get the measurements, then use the first node and the last node to determine the difference,
|
||||
* if there is only one node, or only one node is reading due to errors, then it will use that as both the inlet and outlet so the delta will be 0,
|
||||
*
|
||||
* //NOT IMPLEMENTED YET//
|
||||
* if an ambient is passed in it will use the readings from the ambient as the inlet and the temp component as the outlet
|
||||
*/
|
||||
useEffect(()=>{
|
||||
|
|
@ -52,7 +109,7 @@ export default function GateDeltaTempGraph(props: Props){
|
|||
tempComponent.key(),
|
||||
start.toISOString(),
|
||||
end.toISOString(),
|
||||
500,
|
||||
500,
|
||||
0,
|
||||
"timestamp",
|
||||
).then(resp => {
|
||||
|
|
@ -60,7 +117,24 @@ export default function GateDeltaTempGraph(props: Props){
|
|||
let tempCompMeasurements = resp.data.measurements.map(um => UnitMeasurement.any(um, user));
|
||||
//if there is an ambient component, then treat the temp component like a single sensor and not a chain, and use the ambient measurements as the inlet
|
||||
if(ambient){
|
||||
//TODO: this is just an idea of how to handle a possible path for the overhaul of gates, if that is the route we take then this needs to be coded
|
||||
//need to then load the measurements for the ambient component
|
||||
componentAPI.listUnitMeasurements(
|
||||
deviceID,
|
||||
ambient.key(),
|
||||
start.toISOString(),
|
||||
end.toISOString(),
|
||||
500,
|
||||
0,
|
||||
"timestamp",
|
||||
).then(resp => {
|
||||
let ambientMeasurements = resp.data.measurements.map(um => UnitMeasurement.any(um, user));
|
||||
//now loop through each of them to 'combine' them into a single array
|
||||
const deltas = buildDeltas(ambientMeasurements, tempCompMeasurements, quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE)
|
||||
if(deltas.length > 0){
|
||||
setData(deltas)
|
||||
setRecent(deltas[deltas.length-1])
|
||||
}
|
||||
})
|
||||
|
||||
}else{//else no ambient is passed in treat the temp component like a chain
|
||||
let deltas: SSAreaDataPoint[] = []
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import GateGraphs from "./GateGraphs";
|
|||
//import { ToggleButton, ToggleButtonGroup } from "@material-ui/lab";
|
||||
import { getThemeType } from "theme";
|
||||
import ButtonGroup from "common/ButtonGroup";
|
||||
import { cloneDeep } from "lodash";
|
||||
|
||||
interface Props {
|
||||
gate: Gate;
|
||||
|
|
@ -55,8 +56,11 @@ export default function GateDevice(props: Props) {
|
|||
// const [pcaFanOn, setPCAFanOn] = useState(false);
|
||||
const [detail, setDetail] = useState<"sensors" | "analytics">("analytics");
|
||||
const [lastAmbient, setLastAmbient] = useState(0);
|
||||
const [lastTemps, setLastTemps] = useState({ t1: 0, t2: 0 });
|
||||
const [lastPressures, setLastPressures] = useState({ p1: 0, p2: 0 });
|
||||
//const [lastTemps, setLastTemps] = useState({ t1: 0, t2: 0 });
|
||||
const [ductTemp, setDuctTemp] = useState<number | undefined>()
|
||||
//const [lastPressures, setLastPressures] = useState({ p1: 0, p2: 0 });
|
||||
const [ductPressure, setDuctPressure] = useState<number | undefined>()
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
//addresses of controller LEDs on a V1 device
|
||||
|
|
@ -142,24 +146,20 @@ export default function GateDevice(props: Props) {
|
|||
|
||||
useEffect(() => {
|
||||
let tempComponent = componentOptions.get(tempKey);
|
||||
let t1 = 0;
|
||||
let t2 = 0;
|
||||
if (tempComponent) {
|
||||
if(tempComponent){
|
||||
tempComponent.status.measurement.forEach(um => {
|
||||
let measurement = UnitMeasurement.any(um, user);
|
||||
if (
|
||||
measurement.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE &&
|
||||
measurement.values.length > 0
|
||||
) {
|
||||
let nodeVals = measurement.values[0].values;
|
||||
if (nodeVals.length > 1) {
|
||||
t1 = nodeVals[0]; //uses the first node
|
||||
t2 = nodeVals[1]; //uses second node node
|
||||
if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){
|
||||
let clone = cloneDeep(um)
|
||||
let measurement = UnitMeasurement.any(clone, user)
|
||||
if(measurement.values.length > 0){
|
||||
let readings = measurement.values[measurement.values.length -1]
|
||||
if(readings.values.length > 0){
|
||||
setDuctTemp(readings.values[readings.values.length -1])
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
setLastTemps({ t1, t2 });
|
||||
}, [componentOptions, tempKey, user]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -180,24 +180,20 @@ export default function GateDevice(props: Props) {
|
|||
|
||||
useEffect(() => {
|
||||
let pressureComponent = componentOptions.get(pressureKey);
|
||||
let p1 = 0;
|
||||
let p2 = 0;
|
||||
if (pressureComponent) {
|
||||
if(pressureComponent){
|
||||
pressureComponent.status.measurement.forEach(um => {
|
||||
let measurement = UnitMeasurement.any(um, user);
|
||||
if (
|
||||
measurement.type === quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE &&
|
||||
measurement.values.length > 0
|
||||
) {
|
||||
let nodeVals = measurement.values[0].values;
|
||||
if (nodeVals.length > 1) {
|
||||
p1 = nodeVals[0];
|
||||
p2 = nodeVals[1];
|
||||
if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE){
|
||||
let clone = cloneDeep(um)
|
||||
let measurement = UnitMeasurement.any(clone, user)
|
||||
if(measurement.values.length > 0){
|
||||
let readings = measurement.values[measurement.values.length -1]
|
||||
if(readings.values.length > 0){
|
||||
setDuctPressure(readings.values[readings.values.length -1])
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
setLastPressures({ p1, p2 });
|
||||
}, [componentOptions, pressureKey, user]);
|
||||
|
||||
const ambientSelector = () => {
|
||||
|
|
@ -340,8 +336,10 @@ export default function GateDevice(props: Props) {
|
|||
: 0
|
||||
}
|
||||
ambientTemp={lastAmbient}
|
||||
innerTemps={lastTemps}
|
||||
innerPressures={lastPressures}
|
||||
//innerTemps={lastTemps}
|
||||
//innerPressures={lastPressures}
|
||||
ductTemp={ductTemp}
|
||||
ductPressure={ductPressure}
|
||||
ambientSelector={ambientSelector}
|
||||
tempChainSelector={tempSelector}
|
||||
pressureChainSelector={pressureSelector}
|
||||
|
|
@ -374,9 +372,6 @@ export default function GateDevice(props: Props) {
|
|||
gate={gate}
|
||||
display={detail}
|
||||
compMap={componentOptions}
|
||||
// setPCAState={(state: boolean) => {
|
||||
// setPCAState(state);
|
||||
// }}
|
||||
ambient={ambientKey}
|
||||
pressure={pressureKey}
|
||||
tempChain={tempKey}
|
||||
|
|
|
|||
|
|
@ -303,6 +303,7 @@ export default function GateGraphs(props: Props) {
|
|||
*/
|
||||
const sensorGraphs = () => {
|
||||
const tempComp = compMap.get(tempChain)
|
||||
const ambientComp = compMap.get(ambient)
|
||||
let graphs: JSX.Element[] = []
|
||||
|
||||
//pressure
|
||||
|
|
@ -314,7 +315,7 @@ export default function GateGraphs(props: Props) {
|
|||
|
||||
if(tempComp){
|
||||
graphs.push(
|
||||
<GateDeltaTempGraph deviceID={device} start={startDate} end={endDate} tempComponent={tempComp}/>
|
||||
<GateDeltaTempGraph deviceID={device} start={startDate} end={endDate} tempComponent={tempComp} ambient={ambientComp}/>
|
||||
)
|
||||
}
|
||||
//T/H chain
|
||||
|
|
@ -323,7 +324,6 @@ export default function GateGraphs(props: Props) {
|
|||
graphs.push(graphCard(tempComp, tempReadings))
|
||||
}
|
||||
//ambient
|
||||
let ambientComp = compMap.get(ambient)
|
||||
let ambientReadings = compMeasurements.get(ambient)
|
||||
if (ambientComp && ambientReadings){
|
||||
graphs.push(graphCard(ambientComp, ambientReadings))
|
||||
|
|
@ -345,6 +345,7 @@ export default function GateGraphs(props: Props) {
|
|||
|
||||
const analyticGraphs = () => {
|
||||
let tempComp = compMap.get(tempChain)
|
||||
let ambientComp = compMap.get(ambient)
|
||||
return (
|
||||
<Box>
|
||||
<GateFlowGraph
|
||||
|
|
@ -365,7 +366,7 @@ export default function GateGraphs(props: Props) {
|
|||
/>
|
||||
{/* add a new chart here for the delta temp over time */}
|
||||
{tempComp &&
|
||||
<GateDeltaTempGraph deviceID={device} start={startDate} end={endDate} tempComponent={tempComp}/>
|
||||
<GateDeltaTempGraph deviceID={device} start={startDate} end={endDate} tempComponent={tempComp} ambient={ambientComp}/>
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -164,29 +164,49 @@ export default function GateList(props: Props) {
|
|||
// }
|
||||
|
||||
const conditionDisplay = (gate: Gate) => {
|
||||
console.log(gate)
|
||||
let display = ""
|
||||
let deltaTemp = 0
|
||||
if(gate.status.pcaState === pond.PCAState.PCA_STATE_OFF){
|
||||
display = "Inactive"
|
||||
} else if (gate.status.pcaState === pond.PCAState.PCA_STATE_UNKNOWN){
|
||||
display = "--"
|
||||
} else { //the pca is currently active calulate the delta temp (T2 - T1) provided there are two temps
|
||||
//loop to find the temp readings
|
||||
let clone = cloneDeep(gate.status.lastTempReading)
|
||||
clone.forEach(unitMeasurement => {
|
||||
let um = UnitMeasurement.create(unitMeasurement, user)
|
||||
if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){
|
||||
if(um.values.length > 0){ //as long as there is at least on thing in the measurements
|
||||
let lastReading = um.values[um.values.length-1] //there should only be one measurement in here but just make sure to get the end of the array
|
||||
if(lastReading.values.length > 1){ //make sure there are at least two values in the array
|
||||
console.log(lastReading.values)
|
||||
deltaTemp = lastReading.values[lastReading.values.length -1] - lastReading.values[0] //subtract the first value from the last value to get the delta
|
||||
} else { //the pca is currently active calulate the delta temp (outlet - ambient)
|
||||
let ambient = cloneDeep(gate.status.lastAmbientReading)
|
||||
let temp = cloneDeep(gate.status.lastTempReading)
|
||||
|
||||
if(ambient.length > 0 && temp.length > 0){
|
||||
let ambientTemp: number | undefined
|
||||
let outletTemp: number | undefined
|
||||
ambient.forEach(um => {
|
||||
if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){
|
||||
let clone = cloneDeep(um)
|
||||
let measurement = UnitMeasurement.any(clone, user)
|
||||
if(measurement.values.length > 0){
|
||||
let readings = measurement.values[measurement.values.length -1]
|
||||
if(readings.values.length > 0){
|
||||
ambientTemp = readings.values[readings.values.length -1]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
temp.forEach(um => {
|
||||
if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){
|
||||
let clone = cloneDeep(um)
|
||||
let measurement = UnitMeasurement.any(clone, user)
|
||||
if(measurement.values.length > 0){
|
||||
let readings = measurement.values[measurement.values.length -1]
|
||||
if(readings.values.length > 0){
|
||||
outletTemp = readings.values[readings.values.length -1]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if(ambientTemp && outletTemp){
|
||||
let deltaTemp = outletTemp - ambientTemp
|
||||
display = deltaTemp.toFixed(2) + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C")
|
||||
}else{
|
||||
display = "Sensor Missing"
|
||||
}
|
||||
})
|
||||
display = deltaTemp.toFixed(2) + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C")
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Typography>
|
||||
|
|
|
|||
|
|
@ -43,8 +43,10 @@ const useStyles = makeStyles(() => ({
|
|||
interface Props {
|
||||
finalTemp: number;
|
||||
ambientTemp: number;
|
||||
innerTemps: { t1: number; t2: number };
|
||||
innerPressures: { p1: number; p2: number };
|
||||
//innerTemps: { t1: number; t2: number };
|
||||
//innerPressures: { p1: number; p2: number };
|
||||
ductTemp?: number;
|
||||
ductPressure?: number;
|
||||
ambientSelector: () => JSX.Element;
|
||||
tempChainSelector: () => JSX.Element;
|
||||
pressureChainSelector: () => JSX.Element;
|
||||
|
|
@ -57,8 +59,10 @@ export default function GateSVG(props: Props) {
|
|||
const {
|
||||
finalTemp,
|
||||
ambientTemp,
|
||||
innerTemps,
|
||||
innerPressures,
|
||||
//innerTemps,
|
||||
//innerPressures,
|
||||
ductTemp,
|
||||
ductPressure,
|
||||
ambientSelector,
|
||||
tempChainSelector,
|
||||
pressureChainSelector,
|
||||
|
|
@ -277,12 +281,15 @@ export default function GateSVG(props: Props) {
|
|||
values.push(
|
||||
<g key={"tempChain"} id={"tempChain"} data-name={"tempChain"}>
|
||||
<text fontSize={7} className={classes.fontBase} x={30} y={149}>
|
||||
T1: {innerTemps.t1}
|
||||
{/* T1: {innerTemps.t1}
|
||||
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
||||
? "°F"
|
||||
: "°C"}
|
||||
, T2: {innerTemps.t2}
|
||||
, T1: {innerTemps.t2}
|
||||
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
||||
? "°F"
|
||||
: "°C"} */}
|
||||
Temperature: {ductTemp ?? "N/A"} {user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
||||
? "°F"
|
||||
: "°C"}
|
||||
</text>
|
||||
|
|
@ -292,12 +299,15 @@ export default function GateSVG(props: Props) {
|
|||
values.push(
|
||||
<g key={"pressureChain"} id={"pressureChain"} data-name={"pressureChain"}>
|
||||
<text fontSize={7} className={classes.fontBase} x={30} y={184}>
|
||||
P1: {innerPressures.p1}{" "}
|
||||
{/* P1: {innerPressures.p1}{" "}
|
||||
{user.settings.pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER
|
||||
? "iwg"
|
||||
: "kPa"}
|
||||
, P2: {innerPressures.p2}{" "}
|
||||
{user.settings.pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER
|
||||
? "iwg"
|
||||
: "kPa"} */}
|
||||
Pressure: {ductPressure ?? "N/A"} {user.settings.pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER
|
||||
? "iwg"
|
||||
: "kPa"}
|
||||
</text>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {MenuItem, TextField } from "@mui/material"
|
|||
import { cloneDeep } from "lodash"
|
||||
import { pond } from "protobuf-ts/pond"
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import { getGrainUnit } from "utils"
|
||||
|
||||
interface Props {
|
||||
grainSettings?: pond.GrainSettings
|
||||
|
|
@ -18,7 +19,7 @@ export default function CustomGrainForm(props: Props) {
|
|||
const [constantB, setConstantB] = useState("0")
|
||||
const [constantC, setConstantC] = useState("0")
|
||||
const [kgPerBushel, setKgPerBushel] = useState("0")
|
||||
const [bushelsPerTonne, setBushelsPerTonne] = useState("0")
|
||||
const [bushelsConversion, setBushelConversion] = useState("0")//this is either per tonne or per US ton depending on user preferences
|
||||
const valid = useRef(false)
|
||||
|
||||
useEffect(()=>{
|
||||
|
|
@ -30,12 +31,19 @@ export default function CustomGrainForm(props: Props) {
|
|||
setConstantB(grainSettings.b.toString())
|
||||
setConstantC(grainSettings.c.toString())
|
||||
setKgPerBushel(grainSettings.kgPerBushel.toString())
|
||||
setBushelsPerTonne(grainSettings.bushelsPerTonne.toString())
|
||||
setBushelConversion(grainSettings.bushelsPerTonne.toString())
|
||||
setNewGrainSettings(grainSettings)
|
||||
}
|
||||
},[grainSettings])
|
||||
|
||||
const validate = (name: string, group: string, constA: string, constB: string, constC: string, kgPerBushel: string, bushelsPerTonne: string) => {
|
||||
const validate = (
|
||||
name: string,
|
||||
group: string,
|
||||
constA: string,
|
||||
constB: string,
|
||||
constC: string,
|
||||
kgPerBushel: string,
|
||||
bushelsPerTonne: string) => {
|
||||
if (name === "") return false
|
||||
if (group === "") return false
|
||||
if (isNaN(parseFloat(constA))) return false
|
||||
|
|
@ -64,7 +72,7 @@ export default function CustomGrainForm(props: Props) {
|
|||
setName(name)
|
||||
let settings = cloneDeep(newGrainSettings)
|
||||
settings.name = name
|
||||
valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, bushelsPerTonne)
|
||||
valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, bushelsConversion)
|
||||
settingsChanged(settings)
|
||||
}}/>
|
||||
<TextField
|
||||
|
|
@ -77,7 +85,7 @@ export default function CustomGrainForm(props: Props) {
|
|||
setGroup(group)
|
||||
let settings = cloneDeep(newGrainSettings)
|
||||
settings.group = group
|
||||
valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, bushelsPerTonne)
|
||||
valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, bushelsConversion)
|
||||
settingsChanged(settings)
|
||||
}}/>
|
||||
<TextField
|
||||
|
|
@ -91,7 +99,7 @@ export default function CustomGrainForm(props: Props) {
|
|||
setEquation(enumVal)
|
||||
let settings = cloneDeep(newGrainSettings)
|
||||
settings.equation = enumVal
|
||||
valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, bushelsPerTonne)
|
||||
valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, bushelsConversion)
|
||||
settingsChanged(settings)
|
||||
}}
|
||||
select>
|
||||
|
|
@ -121,7 +129,7 @@ export default function CustomGrainForm(props: Props) {
|
|||
onChange={(e) => {
|
||||
let val = e.target.value
|
||||
setConstantA(val)
|
||||
valid.current = validate(name, group, val, constantB, constantC, kgPerBushel, bushelsPerTonne)
|
||||
valid.current = validate(name, group, val, constantB, constantC, kgPerBushel, bushelsConversion)
|
||||
let settings = cloneDeep(newGrainSettings)
|
||||
if(!isNaN(parseFloat(val))){
|
||||
settings.a = parseFloat(val)
|
||||
|
|
@ -139,7 +147,7 @@ export default function CustomGrainForm(props: Props) {
|
|||
onChange={(e) => {
|
||||
let val = e.target.value
|
||||
setConstantB(val)
|
||||
valid.current = validate(name, group, constantA, val, constantC, kgPerBushel, bushelsPerTonne)
|
||||
valid.current = validate(name, group, constantA, val, constantC, kgPerBushel, bushelsConversion)
|
||||
let settings = cloneDeep(newGrainSettings)
|
||||
if(!isNaN(parseFloat(val))){
|
||||
settings.b = parseFloat(val)
|
||||
|
|
@ -157,7 +165,7 @@ export default function CustomGrainForm(props: Props) {
|
|||
onChange={(e) => {
|
||||
let val = e.target.value
|
||||
setConstantC(val)
|
||||
valid.current = validate(name, group, constantA, constantB, val, kgPerBushel, bushelsPerTonne)
|
||||
valid.current = validate(name, group, constantA, constantB, val, kgPerBushel, bushelsConversion)
|
||||
let settings = cloneDeep(newGrainSettings)
|
||||
if(!isNaN(parseFloat(val))){
|
||||
settings.c = parseFloat(val)
|
||||
|
|
@ -175,7 +183,7 @@ export default function CustomGrainForm(props: Props) {
|
|||
onChange={(e) => {
|
||||
let val = e.target.value
|
||||
setKgPerBushel(val)
|
||||
valid.current = validate(name, group, constantA, constantB, constantC, val, bushelsPerTonne)
|
||||
valid.current = validate(name, group, constantA, constantB, constantC, val, bushelsConversion)
|
||||
let settings = cloneDeep(newGrainSettings)
|
||||
if(!isNaN(parseFloat(val))){
|
||||
settings.kgPerBushel = parseFloat(val)
|
||||
|
|
@ -187,20 +195,24 @@ export default function CustomGrainForm(props: Props) {
|
|||
sx={{marginBottom: 2}}
|
||||
fullWidth
|
||||
type="number"
|
||||
value={bushelsPerTonne}
|
||||
error={isNaN(parseFloat(bushelsPerTonne))}
|
||||
helperText={isNaN(parseFloat(bushelsPerTonne)) ? "Must be a valid number" : ""}
|
||||
value={bushelsConversion}
|
||||
error={isNaN(parseFloat(bushelsConversion))}
|
||||
helperText={isNaN(parseFloat(bushelsConversion)) ? "Must be a valid number" : ""}
|
||||
onChange={(e) => {
|
||||
let val = e.target.value
|
||||
setBushelsPerTonne(val)
|
||||
setBushelConversion(val)
|
||||
valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, val)
|
||||
let settings = cloneDeep(newGrainSettings)
|
||||
if(!isNaN(parseFloat(val))){
|
||||
settings.bushelsPerTonne = parseFloat(val)
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
settings.bushelsPerTonne = parseFloat(val)/0.907
|
||||
}else{
|
||||
settings.bushelsPerTonne = parseFloat(val)
|
||||
}
|
||||
}
|
||||
settingsChanged(settings)
|
||||
}}
|
||||
label="Bushels per Tonne"/>
|
||||
label={"Bushels per " + (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE ? " Tonne" : "Ton")}/>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export interface GrainExtension {
|
|||
colour: string;
|
||||
weightConversionKg: number;
|
||||
bushelsPerTonne: number;
|
||||
bushelsPerTon: number; //NOTE: this is US tons (2000lB/t)
|
||||
}
|
||||
|
||||
const defaultSetTemp = 30.0;
|
||||
|
|
@ -45,7 +46,8 @@ const defaultGrain: GrainExtension = {
|
|||
targetMC: 15.0,
|
||||
colour: "#424242",
|
||||
weightConversionKg: 0,
|
||||
bushelsPerTonne: 0
|
||||
bushelsPerTonne: 0,
|
||||
bushelsPerTon: 0
|
||||
};
|
||||
|
||||
export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||
|
|
@ -64,7 +66,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
targetMC: 15.0,
|
||||
colour: "yellow",
|
||||
weightConversionKg: 0,
|
||||
bushelsPerTonne: 0
|
||||
bushelsPerTonne: 0,
|
||||
bushelsPerTon: 0
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -81,7 +84,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: BarleyImg,
|
||||
colour: "#27632a",
|
||||
weightConversionKg: 21.772657171369,
|
||||
bushelsPerTonne: 45.93
|
||||
bushelsPerTonne: 45.93,
|
||||
bushelsPerTon: 41.66
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -98,7 +102,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
targetMC: 16.1,
|
||||
colour: "#46b298",
|
||||
weightConversionKg: 1,
|
||||
bushelsPerTonne: 45.93
|
||||
bushelsPerTonne: 45.93,
|
||||
bushelsPerTon: 50.63
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -116,7 +121,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
// colour: "#cddc39",
|
||||
colour: "#ffff00",
|
||||
weightConversionKg: 22.67961461,
|
||||
bushelsPerTonne: 44.092
|
||||
bushelsPerTonne: 44.092,
|
||||
bushelsPerTon: 39.99
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -133,7 +139,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: CanolaImg,
|
||||
colour: "#cddc39",
|
||||
weightConversionKg: 27.215537532,
|
||||
bushelsPerTonne: 44.092
|
||||
bushelsPerTonne: 44.092,
|
||||
bushelsPerTon: 39.9
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -150,7 +157,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: CornImg,
|
||||
colour: "#ffef62",
|
||||
weightConversionKg: 25.4014333665971,
|
||||
bushelsPerTonne: 39.368
|
||||
bushelsPerTonne: 39.368,
|
||||
bushelsPerTon: 35.71
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -167,7 +175,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: CornImg,
|
||||
colour: "#ffef62",
|
||||
weightConversionKg: 25.4014333665971,
|
||||
bushelsPerTonne: 39.368
|
||||
bushelsPerTonne: 39.368,
|
||||
bushelsPerTon: 35.71
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -184,7 +193,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: CornImg,
|
||||
colour: "#ffef62",
|
||||
weightConversionKg: 25.4014333665971,
|
||||
bushelsPerTonne: 39.368
|
||||
bushelsPerTonne: 39.368,
|
||||
bushelsPerTon: 35.71
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -201,7 +211,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: CornImg,
|
||||
colour: "#ffef62",
|
||||
weightConversionKg: 25.4014333665971,
|
||||
bushelsPerTonne: 39.368
|
||||
bushelsPerTonne: 39.368,
|
||||
bushelsPerTon: 35.71
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -218,7 +229,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: CornImg,
|
||||
colour: "#ffef62",
|
||||
weightConversionKg: 25.4014333665971,
|
||||
bushelsPerTonne: 39.368
|
||||
bushelsPerTonne: 39.368,
|
||||
bushelsPerTon: 35.71
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -235,7 +247,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: OatImg,
|
||||
colour: "#79955a",
|
||||
weightConversionKg: 15.4222988297197,
|
||||
bushelsPerTonne: 64.842
|
||||
bushelsPerTonne: 64.842,
|
||||
bushelsPerTon: 58.81
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -252,7 +265,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: OatImg,
|
||||
colour: "#79955a",
|
||||
weightConversionKg: 15.4222988297197,
|
||||
bushelsPerTonne: 64.842
|
||||
bushelsPerTonne: 64.842,
|
||||
bushelsPerTon: 58.81
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -269,7 +283,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: OatImg,
|
||||
colour: "#79955a",
|
||||
weightConversionKg: 15.4222988297197,
|
||||
bushelsPerTonne: 64.842
|
||||
bushelsPerTonne: 64.842,
|
||||
bushelsPerTon: 58.81
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -286,7 +301,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: PeanutImg,
|
||||
colour: "#b2a058",
|
||||
weightConversionKg: 23.3124,
|
||||
bushelsPerTonne: 105.263
|
||||
bushelsPerTonne: 105.263,
|
||||
bushelsPerTon: 95.47
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -303,7 +319,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: RiceImg,
|
||||
colour: "#7c9c99",
|
||||
weightConversionKg: 28.439,
|
||||
bushelsPerTonne: 49.02
|
||||
bushelsPerTonne: 49.02,
|
||||
bushelsPerTon: 44.46
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -320,7 +337,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: RiceImg,
|
||||
colour: "#7c9c99",
|
||||
weightConversionKg: 29.979,
|
||||
bushelsPerTonne: 49.02
|
||||
bushelsPerTonne: 49.02,
|
||||
bushelsPerTon: 44.46
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -337,7 +355,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: RiceImg,
|
||||
colour: "#7c9c99",
|
||||
weightConversionKg: 30.744,
|
||||
bushelsPerTonne: 49.02
|
||||
bushelsPerTonne: 49.02,
|
||||
bushelsPerTon: 44.46
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -354,7 +373,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: SorghumImg,
|
||||
colour: "#829baf",
|
||||
weightConversionKg: 25.4014333665971,
|
||||
bushelsPerTonne: 39.368
|
||||
bushelsPerTonne: 39.368,
|
||||
bushelsPerTon: 35.71
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -371,7 +391,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: SoybeanImg,
|
||||
colour: "#7d6d99",
|
||||
weightConversionKg: 27.2158214642112,
|
||||
bushelsPerTonne: 36.744
|
||||
bushelsPerTonne: 36.744,
|
||||
bushelsPerTon: 33.33
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -388,7 +409,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: SunflowerImg,
|
||||
colour: "#ffab40",
|
||||
weightConversionKg: 13.6079107321056,
|
||||
bushelsPerTonne: 73.487
|
||||
bushelsPerTonne: 73.487,
|
||||
bushelsPerTon: 66.65
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -405,7 +427,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: WheatImg,
|
||||
colour: "#46b298",
|
||||
weightConversionKg: 27.2158214642112,
|
||||
bushelsPerTonne: 36.744
|
||||
bushelsPerTonne: 36.744,
|
||||
bushelsPerTon: 33.33
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -422,7 +445,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: WheatImg,
|
||||
colour: "#46b298",
|
||||
weightConversionKg: 27.2158214642112,
|
||||
bushelsPerTonne: 36.744
|
||||
bushelsPerTonne: 36.744,
|
||||
bushelsPerTon: 33.33
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -439,7 +463,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: WheatImg,
|
||||
colour: "#46b298",
|
||||
weightConversionKg: 27.2158214642112,
|
||||
bushelsPerTonne: 36.744
|
||||
bushelsPerTonne: 36.744,
|
||||
bushelsPerTon: 33.33
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -456,7 +481,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: FlaxImg,
|
||||
colour: "#6e6d19",
|
||||
weightConversionKg: 25.4014333665971,
|
||||
bushelsPerTonne: 39.368
|
||||
bushelsPerTonne: 39.368,
|
||||
bushelsPerTon: 35.71
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -473,7 +499,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: FlaxImg,
|
||||
colour: "#6e6d19",
|
||||
weightConversionKg: 25.4014333665971,
|
||||
bushelsPerTonne: 39.368
|
||||
bushelsPerTonne: 39.368,
|
||||
bushelsPerTon: 35.71
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -490,7 +517,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: PeaImg,
|
||||
colour: "#ffe100",
|
||||
weightConversionKg: 27.2158214642112,
|
||||
bushelsPerTonne: 36.744
|
||||
bushelsPerTonne: 36.744,
|
||||
bushelsPerTon: 33.33
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -507,7 +535,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: LentilImg,
|
||||
colour: "#cb5e3c",
|
||||
weightConversionKg: 27.2158214642112,
|
||||
bushelsPerTonne: 36.744
|
||||
bushelsPerTonne: 36.744,
|
||||
bushelsPerTon: 33.33
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -524,7 +553,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: LentilImg,
|
||||
colour: "#cb5e3c",
|
||||
weightConversionKg: 27.2158214642112,
|
||||
bushelsPerTonne: 36.744
|
||||
bushelsPerTonne: 36.744,
|
||||
bushelsPerTon: 33.33
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -541,7 +571,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
img: LentilImg,
|
||||
colour: "#cb5e3c",
|
||||
weightConversionKg: 27.2158214642112,
|
||||
bushelsPerTonne: 36.744
|
||||
bushelsPerTonne: 36.744,
|
||||
bushelsPerTon: 33.33
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
@ -557,7 +588,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
targetMC: 15.0,
|
||||
colour: "red",
|
||||
weightConversionKg: 27.2155,
|
||||
bushelsPerTonne: 36.744
|
||||
bushelsPerTonne: 36.744,
|
||||
bushelsPerTon: 33.33
|
||||
}
|
||||
],[
|
||||
pond.Grain.GRAIN_DRY_BEANS_BLACK,
|
||||
|
|
@ -572,7 +604,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
targetMC: 15.0,
|
||||
colour: "grey",
|
||||
weightConversionKg: 27.2155,
|
||||
bushelsPerTonne: 36.744
|
||||
bushelsPerTonne: 36.744,
|
||||
bushelsPerTon: 33.33
|
||||
}
|
||||
],[
|
||||
pond.Grain.GRAIN_RYE_A,
|
||||
|
|
@ -587,7 +620,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
targetMC: 15.0,
|
||||
colour: "grey",
|
||||
weightConversionKg: 25.4,
|
||||
bushelsPerTonne: 39.368
|
||||
bushelsPerTonne: 39.368,
|
||||
bushelsPerTon: 35.71
|
||||
}
|
||||
],[
|
||||
pond.Grain.GRAIN_RYE_B,
|
||||
|
|
@ -602,7 +636,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
targetMC: 15.0,
|
||||
colour: "grey",
|
||||
weightConversionKg: 25.4,
|
||||
bushelsPerTonne: 39.368
|
||||
bushelsPerTonne: 39.368,
|
||||
bushelsPerTon: 35.71
|
||||
},
|
||||
],[
|
||||
pond.Grain.GRAIN_RYE_C,
|
||||
|
|
@ -617,7 +652,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
targetMC: 15.0,
|
||||
colour: "grey",
|
||||
weightConversionKg: 25.4,
|
||||
bushelsPerTonne: 39.368
|
||||
bushelsPerTonne: 39.368,
|
||||
bushelsPerTon: 35.71
|
||||
},
|
||||
],[
|
||||
pond.Grain.GRAIN_RYE_D,
|
||||
|
|
@ -632,7 +668,8 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
targetMC: 15.0,
|
||||
colour: "grey",
|
||||
weightConversionKg: 25.4,
|
||||
bushelsPerTonne: 39.368
|
||||
bushelsPerTonne: 39.368,
|
||||
bushelsPerTon: 35.71
|
||||
}
|
||||
]
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -225,12 +225,13 @@ export default function GrainTransaction(props: Props) {
|
|||
const createTransaction = (finalVal: number) => {
|
||||
let dest = selectedDestination;
|
||||
let source = selectedSource;
|
||||
let bpt = 1;
|
||||
let bPerTonne = 1;
|
||||
let bPerTon = 1;
|
||||
//use the sources bushel per tonne if one was selected otherwise use the destination
|
||||
if (source) {
|
||||
bpt = source.value.bushelsPerTonne();
|
||||
bPerTonne = source.value.bushelsPerTonne();
|
||||
} else if (dest) {
|
||||
bpt = dest.value.bushelsPerTonne();
|
||||
bPerTonne = dest.value.bushelsPerTonne();
|
||||
}
|
||||
|
||||
let newTransaction = pond.Transaction.create({
|
||||
|
|
@ -239,7 +240,7 @@ export default function GrainTransaction(props: Props) {
|
|||
grainTransaction: pond.GrainTransaction.create({
|
||||
bushels: finalVal,
|
||||
message: grainMessage,
|
||||
bushelsPerTonne: bpt
|
||||
bushelsPerTonne: bPerTonne,
|
||||
})
|
||||
})
|
||||
});
|
||||
|
|
@ -449,6 +450,17 @@ export default function GrainTransaction(props: Props) {
|
|||
);
|
||||
};
|
||||
|
||||
const grainUnitDisplay = () => {
|
||||
switch (getGrainUnit()){
|
||||
case pond.GrainUnit.GRAIN_UNIT_TONNE:
|
||||
return "mT"
|
||||
case pond.GrainUnit.GRAIN_UNIT_TON:
|
||||
return "t"
|
||||
default:
|
||||
return "bu"
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponsiveDialog
|
||||
open={open}
|
||||
|
|
@ -506,14 +518,14 @@ export default function GrainTransaction(props: Props) {
|
|||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? "mT" : "bu"}
|
||||
{grainUnitDisplay()}
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
onChange={e => {
|
||||
//if the user is viewing the grain in weight
|
||||
let grainVal = e.target.value;
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT) {
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) {
|
||||
//need to convert what the user input (weight) into what is actually stored (bushels)
|
||||
//use source of the conversion value if it was selected, otherwise use the destination
|
||||
if (!isNaN(parseFloat(e.target.value))) {
|
||||
|
|
@ -523,6 +535,18 @@ export default function GrainTransaction(props: Props) {
|
|||
grainVal = (+grainVal * selectedDestination.value.bushelsPerTonne()).toFixed(2);
|
||||
}
|
||||
}
|
||||
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) {
|
||||
if (!isNaN(parseFloat(e.target.value))) {
|
||||
if (selectedSource) {
|
||||
if(selectedSource.value.bushelsPerTonne){
|
||||
grainVal = (+grainVal * (selectedSource.value.bushelsPerTonne() * 0.907)).toFixed(2);
|
||||
}
|
||||
} else if (selectedDestination) {
|
||||
if(selectedDestination.value.bushelsPerTonne){
|
||||
grainVal = (+grainVal * (selectedDestination.value.bushelsPerTonne() * 0.907)).toFixed(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setGrainEntry(e.target.value);
|
||||
setGrainChangeVal(grainVal);
|
||||
|
|
|
|||
|
|
@ -36,11 +36,14 @@ export default function GrainBagInventoryGraph(props: Props) {
|
|||
//let time = hist.timestamp;
|
||||
let val = hist.object.grainBagSettings.currentBushels ?? 0;
|
||||
if (
|
||||
getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT &&
|
||||
getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE &&
|
||||
grainBag.bushelsPerTonne() > 1
|
||||
) {
|
||||
val = Math.round((val / grainBag.bushelsPerTonne()) * 100) / 100;
|
||||
}
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){
|
||||
val = Math.round((val / (grainBag.bushelsPerTonne() * 0.907)) * 100) / 100;
|
||||
}
|
||||
if (val !== lastBushels) {
|
||||
lastBushels = val;
|
||||
barData.push({
|
||||
|
|
@ -51,13 +54,16 @@ export default function GrainBagInventoryGraph(props: Props) {
|
|||
}
|
||||
});
|
||||
if (barData.length === 0) {
|
||||
let val = grainBag.bushels()
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1){
|
||||
val = grainBag.bushels() / grainBag.bushelsPerTonne()
|
||||
}
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){
|
||||
val = grainBag.bushels() / (grainBag.bushelsPerTonne() * 0.907)
|
||||
}
|
||||
barData.push({
|
||||
timestamp: moment.now().valueOf(),
|
||||
value:
|
||||
getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT &&
|
||||
grainBag.bushelsPerTonne() > 1
|
||||
? Math.round((grainBag.bushels() / grainBag.bushelsPerTonne()) * 100) / 100
|
||||
: grainBag.bushels()
|
||||
value: Math.round(val*100)/100
|
||||
});
|
||||
}
|
||||
setData(barData);
|
||||
|
|
@ -71,6 +77,17 @@ export default function GrainBagInventoryGraph(props: Props) {
|
|||
}
|
||||
}, [grainBagAPI, grainBag, startDate, endDate, openSnack]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const maxYAxis = () => {
|
||||
let val = grainBag.capacity()
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
||||
val = Math.round((val / grainBag.bushelsPerTonne()) * 100) / 100;
|
||||
}
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){
|
||||
val = Math.round((val / (grainBag.bushelsPerTonne() * 0.907)) * 100) / 100;
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
return (
|
||||
<Card raised>
|
||||
<Box padding={2}>
|
||||
|
|
@ -87,11 +104,7 @@ export default function GrainBagInventoryGraph(props: Props) {
|
|||
}
|
||||
data={data}
|
||||
yMin={0}
|
||||
yMax={
|
||||
getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT
|
||||
? grainBag.capacity() / grainBag.bushelsPerTonne()
|
||||
: grainBag.capacity()
|
||||
}
|
||||
yMax={maxYAxis()}
|
||||
customHeight={customHeight}
|
||||
useGradient
|
||||
labels
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export default function GrainBagSettings(props: Props) {
|
|||
const theme = useTheme();
|
||||
const [grainUpdate, setGrainUpdate] = useState(false);
|
||||
const [grainDiff, setGrainDiff] = useState(0);
|
||||
const [bushelsPerTonne, setBushelsPerTonne] = useState(0);
|
||||
const [bushelConversion, setBushelConversion] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (grainBag) {
|
||||
|
|
@ -84,8 +84,10 @@ export default function GrainBagSettings(props: Props) {
|
|||
: grainBag.settings.length
|
||||
);
|
||||
let grainVal = grainBag.bushels();
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT) {
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) {
|
||||
grainVal = grainBag.bushels() / grainBag.bushelsPerTonne();
|
||||
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) {
|
||||
grainVal = grainBag.bushels() / (grainBag.bushelsPerTonne() * 0.907)
|
||||
}
|
||||
setGrainFill(grainVal.toFixed(2));
|
||||
setBagName(grainBag.name());
|
||||
|
|
@ -117,6 +119,10 @@ export default function GrainBagSettings(props: Props) {
|
|||
|
||||
const submit = () => {
|
||||
//if a bag was passed in do an update
|
||||
let tonneConversion = bushelConversion
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
tonneConversion = bushelConversion / 0.907
|
||||
}
|
||||
if (grainBag) {
|
||||
grainBag.title = bagName;
|
||||
grainBag.settings.initialMoisture = initialMoisture;
|
||||
|
|
@ -127,7 +133,7 @@ export default function GrainBagSettings(props: Props) {
|
|||
grainBag.settings.bushelCapacity = grainCapacity;
|
||||
grainBag.settings.grainSubtype = grainSubtype;
|
||||
grainBag.settings.fillDate = fillDate;
|
||||
grainBag.settings.bushelsPerTonne = bushelsPerTonne;
|
||||
grainBag.settings.bushelsPerTonne = tonneConversion;
|
||||
grainBag.settings.storageType = isCustom
|
||||
? pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN
|
||||
: pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN;
|
||||
|
|
@ -161,6 +167,7 @@ export default function GrainBagSettings(props: Props) {
|
|||
settings.currentBushels = grainBushels;
|
||||
settings.grainSubtype = grainSubtype;
|
||||
settings.fillDate = fillDate;
|
||||
settings.bushelsPerTonne = tonneConversion;
|
||||
settings.storageType = isCustom
|
||||
? pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN
|
||||
: pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN;
|
||||
|
|
@ -205,10 +212,10 @@ export default function GrainBagSettings(props: Props) {
|
|||
/>
|
||||
<TextField
|
||||
label="Bushels Per Tonne"
|
||||
value={bushelsPerTonne}
|
||||
value={bushelConversion}
|
||||
type="number"
|
||||
onChange={event => {
|
||||
setBushelsPerTonne(+event.target.value);
|
||||
setBushelConversion(+event.target.value);
|
||||
}}
|
||||
fullWidth
|
||||
className={classes.bottomSpacing}
|
||||
|
|
@ -227,7 +234,7 @@ export default function GrainBagSettings(props: Props) {
|
|||
? pond.Grain[option.value as keyof typeof pond.Grain]
|
||||
: pond.Grain.GRAIN_INVALID;
|
||||
setSupportedGrainType(newGrainType);
|
||||
setBushelsPerTonne(GrainDescriber(newGrainType).bushelsPerTonne);
|
||||
setBushelConversion(GrainDescriber(newGrainType).bushelsPerTonne);
|
||||
setSelectedGrainOp(option);
|
||||
}}
|
||||
group
|
||||
|
|
@ -246,6 +253,18 @@ export default function GrainBagSettings(props: Props) {
|
|||
return invalid;
|
||||
};
|
||||
|
||||
const grainUnitDisplay = () => {
|
||||
if(bushelConversion > 1){
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
||||
return "mT"
|
||||
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
return "t"
|
||||
}
|
||||
}else{
|
||||
return "bu"
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<GrainTransaction
|
||||
|
|
@ -389,30 +408,24 @@ export default function GrainBagSettings(props: Props) {
|
|||
className={classes.bottomSpacing}
|
||||
/>
|
||||
<TextField
|
||||
label={
|
||||
"Current " +
|
||||
(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && bushelsPerTonne > 0
|
||||
? "Weight"
|
||||
: "Bushels")
|
||||
}
|
||||
label={"Current Inventory"}
|
||||
fullWidth
|
||||
type="number"
|
||||
variant="outlined"
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && bushelsPerTonne > 0
|
||||
? "mT"
|
||||
: "bu"}
|
||||
{grainUnitDisplay()}
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
value={grainFill}
|
||||
onChange={e => {
|
||||
let bushelVal = +e.target.value;
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT) {
|
||||
//convert the number as a weight into the bushel value
|
||||
bushelVal = +e.target.value * (bushelsPerTonne > 0 ? bushelsPerTonne : 1);
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) {
|
||||
//convert the number as a weight into the bushel value, no changing or conversions necessary
|
||||
// since these are both user entered fields and should be the same unit (ton or tonne)
|
||||
bushelVal = +e.target.value * (bushelConversion > 0 ? bushelConversion : 1);
|
||||
}
|
||||
if (bushelVal > grainCapacity) {
|
||||
bushelVal = grainCapacity;
|
||||
|
|
|
|||
|
|
@ -83,12 +83,19 @@ export default function GrainBagVisualizer(props: Props) {
|
|||
const grainOverlay = () => {
|
||||
let displayPending = pendingGrainAmount;
|
||||
let displayDiff = grainDiff;
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && grainBag.bushelsPerTonne() > 1) {
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1) {
|
||||
if (displayPending && displayDiff) {
|
||||
displayPending = Math.round((displayPending / grainBag.bushelsPerTonne()) * 100) / 100;
|
||||
displayDiff = Math.round((displayDiff / grainBag.bushelsPerTonne()) * 100) / 100;
|
||||
}
|
||||
}
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1) {
|
||||
if (displayPending && displayDiff) {
|
||||
displayPending = Math.round((displayPending / (grainBag.bushelsPerTonne() * 0.907)) * 100) / 100;
|
||||
displayDiff = Math.round((displayDiff / (grainBag.bushelsPerTonne()* 0.907)) * 100) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{grainDiff !== undefined && grainDiff > 0 && (
|
||||
|
|
@ -141,7 +148,7 @@ export default function GrainBagVisualizer(props: Props) {
|
|||
};
|
||||
|
||||
const grainAmountDisplay = () => {
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && grainBag.bushelsPerTonne() > 1) {
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1) {
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "row" }}>
|
||||
<Typography variant="body2" style={{ fontWeight: "bold", marginRight: "4px" }}>
|
||||
|
|
@ -150,6 +157,15 @@ export default function GrainBagVisualizer(props: Props) {
|
|||
<Typography variant="body2">({grainBag.fillPercent()}%)</Typography>
|
||||
</div>
|
||||
);
|
||||
} else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1) {
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "row" }}>
|
||||
<Typography variant="body2" style={{ fontWeight: "bold", marginRight: "4px" }}>
|
||||
{(grainBag.bushels() / (grainBag.bushelsPerTonne()*0.907)).toFixed(2) + " t"}
|
||||
</Typography>
|
||||
<Typography variant="body2">({grainBag.fillPercent()}%)</Typography>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "row" }}>
|
||||
|
|
|
|||
123
src/hooks/useWebSocket.ts
Normal file
123
src/hooks/useWebSocket.ts
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import { useEffect, useRef, useCallback } from "react";
|
||||
|
||||
/**
|
||||
* Derives the WebSocket base URL from VITE_APP_API_URL.
|
||||
* e.g. "https://api.brandxtech.ca" -> "wss://api.brandxtech.ca"
|
||||
* Falls back to current page host for local dev.
|
||||
*/
|
||||
function getWsBaseUrl(): string {
|
||||
const apiUrl = import.meta.env.VITE_APP_API_URL;
|
||||
if (apiUrl) {
|
||||
return apiUrl.replace(/^http/, "ws");
|
||||
}
|
||||
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
return `${protocol}//${window.location.host}`;
|
||||
}
|
||||
|
||||
interface UseWebSocketOptions<T> {
|
||||
/** The API path, e.g. "/v1/live/devices/123/components" */
|
||||
path: string;
|
||||
/** Transform the raw MessageEvent into your domain type */
|
||||
parse: (event: MessageEvent) => T;
|
||||
/** Called whenever a new parsed message arrives */
|
||||
onMessage: (data: T) => void;
|
||||
/** Auth token passed as ?token= query param */
|
||||
token?: string;
|
||||
/** Minimum seconds between updates, passed as ?rate= to the backend */
|
||||
rate?: number;
|
||||
/** Whether the connection should be active. Default true. */
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* useWebSocket manages a WebSocket connection with automatic reconnection.
|
||||
*
|
||||
* Usage:
|
||||
* useWebSocket({
|
||||
* path: `/v1/live/devices/${deviceID}/components`,
|
||||
* parse: (e) => Component.any(JSON.parse(e.data)),
|
||||
* onMessage: (component) => handleUpdate(component),
|
||||
* token: authToken,
|
||||
* });
|
||||
*/
|
||||
export function useWebSocket<T>(options: UseWebSocketOptions<T>) {
|
||||
const { path, parse, onMessage, token, rate = 0, enabled = true } = options;
|
||||
|
||||
// Keep latest callbacks in refs so reconnects don't use stale closures
|
||||
const onMessageRef = useRef(onMessage);
|
||||
onMessageRef.current = onMessage;
|
||||
const parseRef = useRef(parse);
|
||||
parseRef.current = parse;
|
||||
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
const retriesRef = useRef(0);
|
||||
|
||||
const connect = useCallback(() => {
|
||||
if (!token) return;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.set("token", token);
|
||||
if (rate > 0) params.set("rate", rate.toString());
|
||||
|
||||
const url = `${getWsBaseUrl()}${path}?${params.toString()}`;
|
||||
const ws = new WebSocket(url);
|
||||
wsRef.current = ws;
|
||||
|
||||
ws.onopen = () => {
|
||||
console.debug(`[ws] connected: ${path}`);
|
||||
retriesRef.current = 0;
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const parsed = parseRef.current(event);
|
||||
onMessageRef.current(parsed);
|
||||
} catch (err) {
|
||||
console.warn(`[ws] parse error on ${path}:`, err);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (err) => {
|
||||
console.warn(`[ws] error on ${path}:`, err);
|
||||
};
|
||||
|
||||
ws.onclose = (event) => {
|
||||
console.debug(`[ws] closed: ${path} (code: ${event.code})`);
|
||||
wsRef.current = null;
|
||||
|
||||
// Don't reconnect on clean close or auth rejection
|
||||
if (event.code === 1000 || event.code === 4001 || event.code === 4003) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Exponential backoff: 1s, 2s, 4s, 8s, ... capped at 30s
|
||||
const delay = Math.min(1000 * Math.pow(2, retriesRef.current), 30000);
|
||||
retriesRef.current += 1;
|
||||
console.debug(`[ws] reconnecting in ${delay}ms...`);
|
||||
reconnectTimeoutRef.current = setTimeout(connect, delay);
|
||||
};
|
||||
}, [path, token, rate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled || !token) {
|
||||
if (wsRef.current) {
|
||||
wsRef.current.close(1000, "disabled");
|
||||
wsRef.current = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
if (reconnectTimeoutRef.current) {
|
||||
clearTimeout(reconnectTimeoutRef.current);
|
||||
}
|
||||
if (wsRef.current) {
|
||||
wsRef.current.close(1000, "cleanup");
|
||||
wsRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [connect, enabled, token]);
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import GrainDescriber from "grain/GrainDescriber";
|
|||
import { cloneDeep } from "lodash";
|
||||
import { MarkerData } from "maps/mapMarkers/Markers";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { getGrainUnit } from "utils";
|
||||
import { stringToMaterialColour } from "utils/strings";
|
||||
import { or } from "utils/types";
|
||||
|
||||
|
|
@ -235,17 +236,14 @@ export class Bin {
|
|||
return bpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the weight in tonnes for the grain inventory provided the bushels per tonne is set
|
||||
* if it is not set it will divide by 1 effectively returning the bushels
|
||||
* @returns grain weight
|
||||
*/
|
||||
public grainTonnes(): number {
|
||||
let weight = 0;
|
||||
if (this.settings.inventory) {
|
||||
weight = this.settings.inventory.grainBushels / this.bushelsPerTonne();
|
||||
public grainInventory(): number {
|
||||
let grain = this.bushels()
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.bushelsPerTonne() > 1){
|
||||
grain = this.bushels() / this.bushelsPerTonne()
|
||||
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.bushelsPerTonne() > 1){
|
||||
grain = this.bushels() / (this.bushelsPerTonne() * 0.907)
|
||||
}
|
||||
return Math.round(weight * 100) / 100;
|
||||
return Math.round(grain*100)/100
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -29,7 +29,13 @@ export class Contract {
|
|||
|
||||
private measurementUnit(): string {
|
||||
if (this.settings.type === pond.ContractType.CONTRACT_TYPE_GRAIN) {
|
||||
return getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? "mT" : "bu";
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1){
|
||||
return "mT"
|
||||
}
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){
|
||||
return "t"
|
||||
}
|
||||
return "bu";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
|
@ -202,9 +208,12 @@ export class Contract {
|
|||
let size = this.settings.size;
|
||||
switch (this.settings.type) {
|
||||
case pond.ContractType.CONTRACT_TYPE_GRAIN:
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && this.conversionValue() > 1) {
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1) {
|
||||
size = size / this.conversionValue();
|
||||
}
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){
|
||||
size = size / (this.conversionValue() * 0.907); //the conversion for grain will be stored in metric tonnes, this will convert it to US tons
|
||||
}
|
||||
}
|
||||
return Math.round(size * 100) / 100;
|
||||
}
|
||||
|
|
@ -213,26 +222,33 @@ export class Contract {
|
|||
let del = this.settings.delivered;
|
||||
switch (this.settings.type) {
|
||||
case pond.ContractType.CONTRACT_TYPE_GRAIN:
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && this.conversionValue() > 1) {
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1) {
|
||||
del = del / this.conversionValue();
|
||||
}
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){
|
||||
del = del / (this.conversionValue() * 0.907); //the conversion for grain will be stored in metric tonnes, this will convert it to US tons
|
||||
}
|
||||
}
|
||||
return Math.round(del * 100) / 100;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static toStoredUnit(
|
||||
secondaryUnitVal: number,
|
||||
contractType: pond.ContractType,
|
||||
conversionValue: number
|
||||
): number {
|
||||
let storedunitVal = secondaryUnitVal;
|
||||
let storedUnitVal = secondaryUnitVal;
|
||||
switch (contractType) {
|
||||
//use the value and conversion they entered directly, it is safe to assume they are both the same unit (ton vs tonne) so dont need to convert anything
|
||||
//before converting to bushels
|
||||
case pond.ContractType.CONTRACT_TYPE_GRAIN:
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && conversionValue > 1) {
|
||||
storedunitVal = secondaryUnitVal * conversionValue;
|
||||
if ((getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) && conversionValue > 1) {
|
||||
storedUnitVal = secondaryUnitVal * conversionValue;
|
||||
}
|
||||
}
|
||||
return storedunitVal;
|
||||
return storedUnitVal;
|
||||
}
|
||||
|
||||
public grainName(): string {
|
||||
|
|
|
|||
|
|
@ -278,6 +278,10 @@ export default function Router() {
|
|||
path="/:groupID/devices/:deviceID"
|
||||
element={<DevicePage />}
|
||||
/>
|
||||
<Route
|
||||
path="/:groupID/devices/:deviceID/components/:componentID"
|
||||
element={<DeviceComponent />}
|
||||
/>
|
||||
<Route
|
||||
path="/:groupID/devices/:deviceID/*"
|
||||
element={<RelativeRoutes />}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,7 @@ import {
|
|||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
darken,
|
||||
Typography,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup
|
||||
} from "@mui/material";
|
||||
import BinActions from "bin/BinActions";
|
||||
import BinHistory from "bin/BinHistory";
|
||||
|
|
@ -60,7 +57,7 @@ import { Ambient } from "models/Ambient";
|
|||
import moment from "moment";
|
||||
import BinStorageConditions from "bin/BinStorageConditions";
|
||||
import BinConditioningCard from "bin/BinConditioningCard";
|
||||
import { makeStyles, styled } from "@mui/styles";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Controller } from "models/Controller";
|
||||
import TaskViewer from "tasks/TaskViewer";
|
||||
|
|
|
|||
|
|
@ -928,9 +928,47 @@ export default function Bins(props: Props) {
|
|||
);
|
||||
};
|
||||
|
||||
const grainUnitDisplay = (custom?: pond.CustomInventory) => {
|
||||
// If custom and not convertible, always bushels
|
||||
if (custom && custom.bushelsPerTonne <= 1) {
|
||||
return " bu";
|
||||
}
|
||||
|
||||
switch (getGrainUnit()) {
|
||||
case pond.GrainUnit.GRAIN_UNIT_TONNE:
|
||||
return " mT";
|
||||
case pond.GrainUnit.GRAIN_UNIT_TON:
|
||||
return " t";
|
||||
default:
|
||||
return " bu";
|
||||
}
|
||||
};
|
||||
|
||||
const customQuantityDisplay = (customInventory: pond.CustomInventory, bushels: number) => {
|
||||
let amount = bushels
|
||||
if(customInventory.bushelsPerTonne > 1){
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
||||
amount = bushels/customInventory.bushelsPerTonne
|
||||
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
amount = bushels/(customInventory.bushelsPerTonne*0.907)
|
||||
}
|
||||
}
|
||||
return Math.round(amount*100)/100
|
||||
}
|
||||
|
||||
const supportedGrainDisplay = (grain: pond.Grain, bushels: number) => {
|
||||
const describer = GrainDescriber(grain)
|
||||
let amount = bushels
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
||||
amount = bushels/describer.bushelsPerTonne
|
||||
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
||||
amount = bushels/describer.bushelsPerTon
|
||||
}
|
||||
return Math.round(amount*100)/100
|
||||
}
|
||||
|
||||
const binUtilizationList = () => {
|
||||
const hasInventory = binMetrics && binMetrics.grainInventory.length > 0;
|
||||
const useWeight = getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT;
|
||||
return (
|
||||
<Accordion
|
||||
className={classes.accordion}
|
||||
|
|
@ -972,17 +1010,9 @@ export default function Bins(props: Props) {
|
|||
<ImageListItem key={key}>
|
||||
<BinUtilizationChart
|
||||
grain={inv.grainType}
|
||||
customUnit={useWeight ? " mT" : " bu"}
|
||||
bushelAmount={
|
||||
useWeight
|
||||
? inv.bushelAmount / GrainDescriber(inv.grainType).bushelsPerTonne
|
||||
: inv.bushelAmount
|
||||
}
|
||||
bushelCapacity={
|
||||
useWeight
|
||||
? inv.bushelCapacity / GrainDescriber(inv.grainType).bushelsPerTonne
|
||||
: inv.bushelCapacity
|
||||
}
|
||||
customUnit={grainUnitDisplay()}
|
||||
bushelAmount={supportedGrainDisplay(inv.grainType, inv.bushelAmount)}
|
||||
bushelCapacity={supportedGrainDisplay(inv.grainType, inv.bushelCapacity)}
|
||||
onClick={() => {
|
||||
setContentFilter(pond.Grain[inv.grainType]);
|
||||
loadMoreBins(pond.Grain[inv.grainType]);
|
||||
|
|
@ -1001,11 +1031,10 @@ export default function Bins(props: Props) {
|
|||
amount = amount * 35.239;
|
||||
cap = cap * 35.239;
|
||||
unit = " L";
|
||||
}
|
||||
if (useWeight && inv.bushelsPerTonne > 1) {
|
||||
amount = amount / inv.bushelsPerTonne;
|
||||
cap = cap / inv.bushelsPerTonne;
|
||||
unit = " mT";
|
||||
} else {
|
||||
amount = customQuantityDisplay(inv, amount)
|
||||
cap = customQuantityDisplay(inv, cap)
|
||||
unit = grainUnitDisplay(inv);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { useDeviceAPI, useGlobalState, useSnackbar } from "providers";
|
|||
import { useEffect, useState } from "react";
|
||||
import { useLocation, useParams } from "react-router-dom";
|
||||
import PageContainer from "./PageContainer";
|
||||
import { useMobile } from "hooks";
|
||||
import { useHTTP, useMobile } from "hooks";
|
||||
import LoadingScreen from "app/LoadingScreen";
|
||||
import SmartBreadcrumb from "common/SmartBreadcrumb";
|
||||
import DeviceActions from "device/DeviceActions";
|
||||
|
|
@ -21,6 +21,7 @@ import { or } from "utils";
|
|||
import ComponentDiagnostics from "component/ComponentDiagnostics";
|
||||
import DeviceWizard from "device/DeviceWizard";
|
||||
import DeviceScannedComponents from "device/autoDetect/DeviceScannedComponents";
|
||||
import { useWebSocket } from "hooks/useWebSocket";
|
||||
|
||||
export interface DevicePageData {
|
||||
device: Device;
|
||||
|
|
@ -65,6 +66,8 @@ export default function DevicePage() {
|
|||
|
||||
// const [devicePageData, setDevicePageData] = useState(pond.GetDevicePageDataResponse.create())
|
||||
|
||||
const { token } = useHTTP();
|
||||
|
||||
const loadDevice = () => {
|
||||
if (loading) return
|
||||
if (state?.devicePageData) {
|
||||
|
|
@ -165,6 +168,41 @@ export default function DevicePage() {
|
|||
.finally(() => setLoading(false));
|
||||
}
|
||||
|
||||
// --- Real-time component updates ---
|
||||
// Streams all component changes for this device.
|
||||
// When the backend receives a new reading and updates a component,
|
||||
// this fires and updates the component in state without a page refresh.
|
||||
useWebSocket<{ key: string; component: Component }>({
|
||||
path: `/live/devices/${deviceID}/components`,
|
||||
parse: (e) => {
|
||||
const raw = JSON.parse(e.data);
|
||||
const comp = Component.any(raw);
|
||||
return { key: comp.key(), component: comp };
|
||||
},
|
||||
onMessage: ({ key, component }) => {
|
||||
// Functional update so we always work with the latest state
|
||||
setComponents((prev) => {
|
||||
const updated = new Map(prev);
|
||||
updated.set(key, component);
|
||||
return updated;
|
||||
});
|
||||
},
|
||||
token,
|
||||
enabled: !!deviceID,
|
||||
});
|
||||
|
||||
// --- Real-time device updates (optional) ---
|
||||
// Updates device-level info (name, status, lastActive, etc.)
|
||||
useWebSocket<Device>({
|
||||
path: `/live/devices/${deviceID}`,
|
||||
parse: (e) => Device.any(JSON.parse(e.data)),
|
||||
onMessage: (updatedDevice) => {
|
||||
setDevice(updatedDevice);
|
||||
},
|
||||
token,
|
||||
enabled: !!deviceID,
|
||||
});
|
||||
|
||||
const loadPortScan = () => {
|
||||
deviceAPI.listFoundComponents(parseInt(deviceID))
|
||||
.then(resp => {
|
||||
|
|
|
|||
|
|
@ -168,7 +168,8 @@ export default function SignupCallback () {
|
|||
variant="outlined"
|
||||
InputLabelProps={{ shrink: true }}>
|
||||
<MenuItem value={pond.GrainUnit.GRAIN_UNIT_BUSHELS}>Bushels (bu)</MenuItem>
|
||||
<MenuItem value={pond.GrainUnit.GRAIN_UNIT_WEIGHT}>Tonnes (mT)</MenuItem>
|
||||
<MenuItem value={pond.GrainUnit.GRAIN_UNIT_TONNE}>Tonnes (mT)</MenuItem>
|
||||
<MenuItem value={pond.GrainUnit.GRAIN_UNIT_TON}>US Tons (t)</MenuItem>
|
||||
</TextField>
|
||||
</Grid2>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ export default function GrainBag(props: Props) {
|
|||
}
|
||||
console.log
|
||||
userAPI.getUser(user.id(), { key: key, kind: kind } as Scope).then(resp => {
|
||||
console.log(resp)
|
||||
setPermissions(resp.permissions);
|
||||
});
|
||||
}, [as, bagID, userAPI, user]);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ interface IHTTPContext {
|
|||
) => Promise<AxiosResponse<T>>;
|
||||
del: <T>(url: string, spreadOptions?: AxiosRequestConfig) => Promise<AxiosResponse<T>>;
|
||||
options: (demo?: boolean) => AxiosRequestConfig;
|
||||
token: string;
|
||||
}
|
||||
|
||||
interface Props extends PropsWithChildren<any>{
|
||||
|
|
@ -104,7 +105,8 @@ export default function HTTPProvider(props: Props) {
|
|||
put,
|
||||
post,
|
||||
del,
|
||||
options: defaultOptions
|
||||
options: defaultOptions,
|
||||
token
|
||||
}}>
|
||||
{/* <BillingProvider> */}
|
||||
<PondProvider>
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ export default function BinProvider(props: PropsWithChildren<Props>) {
|
|||
) => {
|
||||
const view = otherTeam ? otherTeam : as
|
||||
let url = "/bins/" + bin + "/updateComponent/" + component + "/preferences";
|
||||
if (view) url = url + "/?as=" + view;
|
||||
if (view) url = url + "?as=" + view;
|
||||
interface request {
|
||||
preferences: Object;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,6 @@ export default function GateProvider(props: PropsWithChildren<Props>) {
|
|||
return new Promise<AxiosResponse<pond.ListGatesResponse>>((resolve, reject) => {
|
||||
get<pond.ListGatesResponse>(url).then(resp => {
|
||||
resp.data = pond.ListGatesResponse.fromObject(resp.data)
|
||||
console.log(resp)
|
||||
return resolve(resp)
|
||||
}).catch(err => {
|
||||
return reject(err)
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export default function GrainBagProvider(props: PropsWithChildren<Props>) {
|
|||
(end && "&end=" + end)
|
||||
)
|
||||
if (view) {
|
||||
url = url + "?as=" + view
|
||||
url = url + "&as=" + view
|
||||
}
|
||||
return get<pond.ListGrainBagHistoryResponse>(url);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ export function getWhitelabel(): WhiteLabel {
|
|||
return BXT_WHITE_LABEL;
|
||||
}
|
||||
if (window.location.origin.includes("localhost")) {
|
||||
return MIPCA_WHITE_LABEL;
|
||||
return STREAMLINE_WHITE_LABEL;
|
||||
}
|
||||
if (window.location.origin.includes("staging") || import.meta.env.VITE_LOCAL_STAGING=='true') {
|
||||
return STAGING_WHITELABEL;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,17 @@ interface Props {
|
|||
|
||||
export default function TransactionDataDisplay(props: Props) {
|
||||
const { transaction } = props;
|
||||
console.log(transaction)
|
||||
|
||||
const grainDisplay = (gt: pond.GrainTransaction) => {
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && gt.bushelsPerTonne > 1){
|
||||
return "Weight: " + Math.round(gt.bushels / gt.bushelsPerTonne*100)/100 + " mT"
|
||||
}
|
||||
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && gt.bushelsPerTonne > 1){
|
||||
return "Weight: " + Math.round(gt.bushels / (gt.bushelsPerTonne*0.907)*100)/100 + " t"
|
||||
}
|
||||
return "Bushels: " + gt.bushels
|
||||
}
|
||||
|
||||
const display = () => {
|
||||
let transactionData = transaction.transaction.transaction;
|
||||
|
|
@ -27,9 +38,7 @@ export default function TransactionDataDisplay(props: Props) {
|
|||
</Typography>
|
||||
<Typography>Variant: {gt.subtype}</Typography>
|
||||
<Typography>
|
||||
{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && gt.bushelsPerTonne > 1
|
||||
? "Weight: " + gt.bushels / gt.bushelsPerTonne + " mT"
|
||||
: "Bushels: " + gt.bushels}
|
||||
{grainDisplay(gt)}
|
||||
</Typography>
|
||||
<Typography>Message: {gt.message}</Typography>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -455,8 +455,11 @@ export default function UserSettings(props: Props) {
|
|||
case 1:
|
||||
grainUnit = pond.GrainUnit.GRAIN_UNIT_BUSHELS;
|
||||
break;
|
||||
case 2:
|
||||
grainUnit = pond.GrainUnit.GRAIN_UNIT_WEIGHT;
|
||||
case 3:
|
||||
grainUnit = pond.GrainUnit.GRAIN_UNIT_TONNE;
|
||||
break;
|
||||
case 4:
|
||||
grainUnit = pond.GrainUnit.GRAIN_UNIT_TON;
|
||||
break;
|
||||
default:
|
||||
grainUnit = pond.GrainUnit.GRAIN_UNIT_UNKNOWN;
|
||||
|
|
@ -540,7 +543,8 @@ export default function UserSettings(props: Props) {
|
|||
variant="outlined"
|
||||
InputLabelProps={{ shrink: true }}>
|
||||
<MenuItem value={pond.GrainUnit.GRAIN_UNIT_BUSHELS}>Bushels (bu)</MenuItem>
|
||||
<MenuItem value={pond.GrainUnit.GRAIN_UNIT_WEIGHT}>Tonnes (mT)</MenuItem>
|
||||
<MenuItem value={pond.GrainUnit.GRAIN_UNIT_TONNE}>Tonnes (mT)</MenuItem>
|
||||
<MenuItem value={pond.GrainUnit.GRAIN_UNIT_TON}>US Tons (t)</MenuItem>
|
||||
</TextField>
|
||||
</Grid2>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -55,13 +55,33 @@ export function setDistanceUnit(unit: pond.DistanceUnit) {
|
|||
}
|
||||
|
||||
export function getGrainUnit(): pond.GrainUnit {
|
||||
return localStorage.getItem("grainUnit") === "mT"
|
||||
? pond.GrainUnit.GRAIN_UNIT_WEIGHT
|
||||
: pond.GrainUnit.GRAIN_UNIT_BUSHELS;
|
||||
switch(localStorage.getItem("grainUnit")){
|
||||
case "mT":
|
||||
return pond.GrainUnit.GRAIN_UNIT_TONNE
|
||||
case "t":
|
||||
return pond.GrainUnit.GRAIN_UNIT_TON
|
||||
default:
|
||||
return pond.GrainUnit.GRAIN_UNIT_BUSHELS
|
||||
}
|
||||
|
||||
// return localStorage.getItem("grainUnit") === "mT"
|
||||
// ? pond.GrainUnit.GRAIN_UNIT_WEIGHT
|
||||
// : pond.GrainUnit.GRAIN_UNIT_BUSHELS;
|
||||
}
|
||||
|
||||
export function setGrainUnit(unit: pond.GrainUnit) {
|
||||
localStorage.setItem("grainUnit", unit === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? "mT" : "bu");
|
||||
switch(unit){
|
||||
case pond.GrainUnit.GRAIN_UNIT_WEIGHT:
|
||||
case pond.GrainUnit.GRAIN_UNIT_TONNE:
|
||||
localStorage.setItem("grainUnit", "mT")
|
||||
break;
|
||||
case pond.GrainUnit.GRAIN_UNIT_TON:
|
||||
localStorage.setItem("grainUnit", "t")
|
||||
break;
|
||||
default:
|
||||
localStorage.setItem("grainUnit", "bu")
|
||||
}
|
||||
// localStorage.setItem("grainUnit", unit === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? "mT" : "bu");
|
||||
}
|
||||
|
||||
export const distanceConversion = (val: number) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue