created a change grain dialog component and made the grain display a 'button' to be able to change the grain without going into the settings

This commit is contained in:
csawatzky 2026-06-02 15:20:04 -06:00
parent dc52ef77a7
commit b8a49197cb
3 changed files with 263 additions and 107 deletions

View file

@ -1,4 +1,4 @@
import { Box, Card, Grid2, LinearProgress, Typography } from "@mui/material";
import { Box, Card, DialogContent, DialogTitle, Grid2, IconButton, LinearProgress, Switch, Typography } from "@mui/material";
import { useMobile } from "hooks";
import { Bin, Component, Device } from "models";
import Bin3dVisualizer from "./components/bin3dVisualizer";
@ -12,6 +12,10 @@ import { pond } from "protobuf-ts/pond";
import BinDetails from "./components/binDetails";
import { Plenum } from "models/Plenum";
import BinSensorsDisplay from "../binSensorsDisplay";
import ResponsiveDialog from "common/ResponsiveDialog";
import { useState } from "react";
import SearchSelect from "common/SearchSelect";
import ChangeGrainDialog from "grain/ChangeGrainDialog";
interface Props {
bin: Bin
@ -34,6 +38,7 @@ export default function BinSummary(props: Props){
const {bin, devices, fans, heaters, permissions, componentDevices, componentMap, binPrefs, updateBinCallback, cables = [], plenums = [], setPreferences} = props
//const [currentDevice, setCurrentDevice] = useState<Device | undefined>(undefined)
const isMobile = useMobile()
const [openGrainDialog, setOpenGrainDialog] = useState(false)
// useEffect(() => {
// if(devices.length > 0){
@ -71,31 +76,52 @@ export default function BinSummary(props: Props){
)
}
const inventorySummary = () => {
return (
<Box padding={2}>
<Card raised sx={{ marginBottom: 2 }}>
<Grid2 container justifyContent={"space-between"} sx={{padding:2}}>
<Grid2>
<Box display="flex" justifyContent="space-between" alignItems="flex-start" sx={{ padding: 2 }}>
{/* Grain Type */}
<Box>
<Typography variant="caption" sx={{ color: grey[500] }}>
Grain Type
</Typography>
<Box display="flex" alignItems="center">
<Box
display="flex"
alignItems="center"
gap={0.5}
onClick={() => setOpenGrainDialog(true)}
sx={{
cursor: "pointer",
mt: 0.5,
px: 1,
py: 0.5,
borderRadius: 1,
border: "1px solid",
borderColor: grey[700],
"&:hover": {
borderColor: grey[500],
backgroundColor: "rgba(255,255,255,0.05)",
},
}}
>
<Spa sx={{ color: "#4caf50", fontSize: 18 }} />
{/* <GrassIcon sx={{ color: "#4caf50", fontSize: 18 }} /> */}
<Typography variant="body1">{bin.grainName()}</Typography>
</Box>
</Grid2>
<Grid2 sx={{ flexGrow: 1, maxWidth: 400 }}>
</Box>
{/* Bin Fill */}
<Box sx={{ flexGrow: 1, maxWidth: 400, mx: 4 }}>
<Typography variant="caption" sx={{ color: grey[500] }}>
Bin Fill
</Typography>
<Box display="flex" alignItems="center" gap={1}>
<Box display="flex" alignItems="center" gap={1} sx={{ mt: 0.5 }}>
<Typography variant="body2" noWrap>
{bin.binFillCap()}
</Typography>
<LinearProgress
variant="determinate"
value={bin.fillPercent()} // adjust to your data
value={bin.fillPercent()}
sx={{
flexGrow: 1,
height: 8,
@ -108,31 +134,14 @@ export default function BinSummary(props: Props){
}}
/>
</Box>
</Grid2>
<Grid2>
</Box>
{/* Last Updated */}
<Box>
<Typography variant="caption" sx={{ color: grey[500] }}>
Last Updated
</Typography>
{/* this would be to show the last time the device checked in */}
{/* {currentDevice ?
<Box display="flex" alignItems="center" gap={2}>
<Box display="flex" alignItems="center" gap={1}>
<AccessTime sx={{ fontSize: 16, color: grey[400] }} />
<Typography variant="body2" sx={{ color: grey[300] }}>
{moment(currentDevice.status.lastActive).fromNow()}
</Typography>
</Box>
{activity(currentDevice.status.lastActive)}
</Box>
:
<Box display="flex" alignItems="center">
<Typography variant="body2" sx={{ color: "grey.300" }}>
No Connected Devices Found
</Typography>
</Box>
} */}
{/* this shows the last time the bins status updated */}
<Box display="flex" alignItems="center" gap={2}>
<Box display="flex" alignItems="center" gap={2} sx={{ mt: 0.5 }}>
<Box display="flex" alignItems="center" gap={1}>
<AccessTime sx={{ fontSize: 16, color: grey[400] }} />
<Typography variant="body2" sx={{ color: grey[300] }}>
@ -141,9 +150,16 @@ export default function BinSummary(props: Props){
</Box>
{activity(bin.status.timestamp)}
</Box>
</Grid2>
</Grid2>
</Box>
</Box>
</Card>
);
};
return (
<Box padding={2}>
<ChangeGrainDialog bin={bin} open={openGrainDialog} closeDialog={()=>setOpenGrainDialog(false)} permissions={permissions} updateBin={updateBinCallback}/>
{inventorySummary()}
<Grid2 container spacing={2}>
<Grid2 size={isMobile ? 12 : 7}>
<Card raised sx={{height: 600}}>

View file

@ -394,41 +394,6 @@ export default function BinTableView(props: Props) {
{levelDisplay(level)}
</TableRow>
))}
<TableRow>
<TableCell>TESTING</TableCell>
<TableCell>SCROLLING</TableCell>
<TableCell>ROWS</TableCell>
</TableRow>
<TableRow>
<TableCell>TESTING</TableCell>
<TableCell>SCROLLING</TableCell>
<TableCell>ROWS</TableCell>
</TableRow>
<TableRow>
<TableCell>TESTING</TableCell>
<TableCell>SCROLLING</TableCell>
<TableCell>ROWS</TableCell>
</TableRow>
<TableRow>
<TableCell>TESTING</TableCell>
<TableCell>SCROLLING</TableCell>
<TableCell>ROWS</TableCell>
</TableRow>
<TableRow>
<TableCell>TESTING</TableCell>
<TableCell>SCROLLING</TableCell>
<TableCell>ROWS</TableCell>
</TableRow>
<TableRow>
<TableCell>TESTING</TableCell>
<TableCell>SCROLLING</TableCell>
<TableCell>ROWS</TableCell>
</TableRow>
<TableRow>
<TableCell>TESTING</TableCell>
<TableCell>SCROLLING</TableCell>
<TableCell>ROWS</TableCell>
</TableRow>
</TableBody>
</Table>
</Box>

View file

@ -0,0 +1,175 @@
import { Box, Button, DialogActions, DialogContent, DialogTitle, Grid2, Switch, TextField } from "@mui/material";
import ResponsiveDialog from "common/ResponsiveDialog";
import SearchSelect, { Option } from "common/SearchSelect";
import { Bin } from "models";
import { pond } from "protobuf-ts/pond";
import GrainDescriber, { GrainOptions, ToGrainOption } from "grain/GrainDescriber";
import { useEffect, useMemo, useState } from "react";
import React from "react";
import CustomGrainSelector from "./CustomGrainSelector";
import { cloneDeep } from "lodash";
import { useBinAPI, useSnackbar } from "providers";
interface Props {
bin: Bin
open: boolean
closeDialog: () => void
permissions: pond.Permission[]
/**
* this function can be passed in if you want to just return a new bin with the changes and handle the actual update in the parent,
* it will stop the component from doing the update itself
* @param bin the bin with the new changes
* @returns
*/
updateBin?: (bin: Bin) => void
/**
* this function can be passed in as a callback function if the component handles the update
* @param updated whether the bin api was used to update the bin
* @returns
*/
updateCallback?: (bin: Bin) => void
}
export default function ChangeGrainDialog(props: Props){
const { bin, open, permissions, closeDialog, updateBin, updateCallback } = props
const canEdit = useMemo(()=>{
return permissions ? permissions.includes(pond.Permission.PERMISSION_WRITE) : false;
},[permissions])
const [isCustom, setIsCustom] = useState(false)
const grainOptions = GrainOptions();
const [grainOption, setGrainOption] = useState<Option>();
const [newGrainType, setNewGrainType] = useState<pond.Grain>(0);
const [newGrainSettings, setNewGrainSettings] = useState<pond.GrainSettings | undefined>()
const binAPI = useBinAPI()
const {openSnack} = useSnackbar()
const [grainSubtype, setGrainSubtype] = useState("")
useEffect(()=>{
setGrainOption(ToGrainOption(bin.grain()))
setIsCustom(bin.grain() === pond.Grain.GRAIN_CUSTOM)
setGrainSubtype(bin.subtype())
setNewGrainType(bin.grain())
},[bin])
/**
* this function is where the split happens,
* if update bin is passed in it will just clone the bin, change the settings and pass the updated clone to the parent so that it can handle the api call
* otherwise the component will update the bin itself through the bin api and upon completion pass that updated bin to the parent if successfull
* in the event both functions are passed in the parent update will be given priority and the internal update will not occur
*/
const updateBinSettings = () => {
let clone = cloneDeep(bin)
let inventory = clone.settings.inventory ?? pond.BinInventory.create()
inventory.grainType = newGrainType
inventory.customGrain = newGrainSettings
inventory.grainSubtype = grainSubtype
if(updateBin){
//simply pass the clone up to the parent and it can deal with it
updateBin(clone)
}else if (updateCallback) {
//use the actual bin api to update the bin inside the component and upon completion return the updated bin
binAPI.updateBin(bin.key(), clone.settings).then(resp => {
openSnack("Updated the grain type")
updateCallback(clone)
}).catch(err => {
openSnack("There was a problem changing the grain type")
})
}
}
//closes the dialog
const close = () => {
closeDialog()
}
return (
<ResponsiveDialog
open={open}
onClose={close}>
<DialogTitle>Change Grain Type</DialogTitle>
<DialogContent>
<Grid2 container justifyContent="space-between" alignItems="center">
<Grid2>
<Grid2 container alignItems="center">
<Grid2 >Grain</Grid2>
<Grid2 >
<Switch
color="default"
value={isCustom}
checked={isCustom}
onChange={(_, checked) => {
setIsCustom(checked)
if(checked){
setNewGrainType(pond.Grain.GRAIN_CUSTOM)
}else{
setNewGrainType(bin.grain())
setNewGrainSettings(undefined)
}
}}
name="storage"
/>
</Grid2>
<Grid2 >Custom</Grid2>
</Grid2>
</Grid2>
</Grid2>
{!isCustom && (
<Box>
<SearchSelect
label="Type"
selected={grainOption}
changeSelection={option => {
let newGrainType = option
? pond.Grain[option.value as keyof typeof pond.Grain]
: pond.Grain.GRAIN_INVALID;
setGrainOption(ToGrainOption(newGrainType));
setNewGrainType(newGrainType);
//setBushPerTonne(GrainDescriber(newGrainType).bushelsPerTonne.toFixed(2));
}}
group
disabled={!canEdit}
options={grainOptions}
/>
</Box>
)}
{isCustom ? (
<React.Fragment>
<CustomGrainSelector initialGrain={bin.customGrain()} onGrainSettingsChange={settings => {setNewGrainSettings(settings)}}/>
</React.Fragment>
) : (
<TextField
sx={{marginTop: 2}}
label="Grain Variant"
value={grainSubtype}
type="text"
onChange={event => {
setGrainSubtype(event.target.value);
}}
fullWidth
variant="outlined"
disabled={!grainOption}
/>
)}
</DialogContent>
<DialogActions>
<Button
onClick={() => {
close();
}}>
Cancel
</Button>
<Button
variant="contained"
color="primary"
// disabled={grainErrorCheck()}
onClick={() => {
updateBinSettings();
close()
}}>
Confirm
</Button>
</DialogActions>
</ResponsiveDialog>
);
}