finished some tweaks for the new bin page
This commit is contained in:
parent
87382dd5d8
commit
2cffc20daa
10 changed files with 225 additions and 130 deletions
|
|
@ -172,8 +172,9 @@ export function BuildCableData(bin: Bin, dims: BinDims): CableData[] {
|
|||
distributed.forEach(orbit => {
|
||||
const count = orbit.assignedCount ?? 0;
|
||||
|
||||
// const topY = getTopY(orbit.radius, dims); //this is if we want the cables to start at the roof
|
||||
const topY = dims.sidewallHeight/2 //this will start the cables at the top of the sidewall
|
||||
const topY = getTopY(orbit.radius, dims); //this is if we want the cables to start at the roof
|
||||
//const topY = dims.sidewallHeight/2 //this will start the cables at the top of the sidewall
|
||||
|
||||
const bottomY = getBottomY(orbit.radius, dims);
|
||||
|
||||
if (orbit.orbit === 0 && count > 0) {
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ export default function Bin3dView(props: Props) {
|
|||
dims.diameter,
|
||||
dims.sidewallHeight,
|
||||
dims.hopperHeight,
|
||||
dims.roofHeight,
|
||||
fillPercent
|
||||
);
|
||||
}, [isCableInventory, fillPercent, dims]);
|
||||
|
|
|
|||
|
|
@ -133,11 +133,18 @@ export default function MoistureHeatMap({ binDimensions, targetMoisture, nodes,
|
|||
const binRadius = binDimensions.diameter / 2;
|
||||
const sidewallHeight = binDimensions.sidewallHeight;
|
||||
const hopperHeight = binDimensions.hopperHeight ?? 0;
|
||||
const roofHeight = binDimensions.roofHeight ?? 0;
|
||||
|
||||
const sidewallBaseY = -sidewallHeight / 2;
|
||||
const sidewallTopY = sidewallHeight / 2;
|
||||
const hopperTipY = sidewallBaseY - hopperHeight;
|
||||
const roofTipY = sidewallTopY + roofHeight;
|
||||
|
||||
const maxRadiusAtY = (y: number) => {
|
||||
if (y > sidewallTopY) {
|
||||
if (roofHeight <= 0 || y >= roofTipY) return 0;
|
||||
return binRadius * (1 - (y - sidewallTopY) / roofHeight);
|
||||
}
|
||||
if (y >= sidewallBaseY) return binRadius;
|
||||
if (hopperHeight <= 0 || y <= hopperTipY) return 0;
|
||||
return binRadius * ((y - hopperTipY) / hopperHeight);
|
||||
|
|
@ -191,7 +198,7 @@ export default function MoistureHeatMap({ binDimensions, targetMoisture, nodes,
|
|||
const getSurfaceY = (x: number, z: number): number => {
|
||||
if (surfaceAnchors.length > 0) {
|
||||
const raw = idwSurfaceY(x, z, surfaceAnchors, SURFACE_IDW_POWER);
|
||||
return Math.max(grainFloorY, Math.min(sidewallHeight / 2, raw));
|
||||
return Math.max(grainFloorY, Math.min(roofTipY, raw));
|
||||
}
|
||||
return flatMaxY ?? sidewallBaseY;
|
||||
};
|
||||
|
|
@ -415,6 +422,8 @@ export default function MoistureHeatMap({ binDimensions, targetMoisture, nodes,
|
|||
binRadius,
|
||||
targetMoisture,
|
||||
hopperHeight,
|
||||
roofHeight,
|
||||
roofTipY,
|
||||
flatMaxY,
|
||||
flatMinY,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -133,13 +133,23 @@ export default function TempHeatMap({ binDimensions, targetTemp, nodes, flatMaxY
|
|||
const binRadius = binDimensions.diameter / 2;
|
||||
const sidewallHeight = binDimensions.sidewallHeight;
|
||||
const hopperHeight = binDimensions.hopperHeight ?? 0;
|
||||
const roofHeight = binDimensions.roofHeight ?? 0;
|
||||
const upperThreshold = targetTemp;
|
||||
|
||||
const sidewallBaseY = -sidewallHeight / 2;
|
||||
const sidewallTopY = sidewallHeight / 2;
|
||||
const hopperTipY = sidewallBaseY - hopperHeight;
|
||||
|
||||
const roofTipY = sidewallTopY + roofHeight;
|
||||
|
||||
const maxRadiusAtY = (y: number) => {
|
||||
// Inside the roof cone — tapers from binRadius at sidewallTopY to 0 at roofTipY
|
||||
if (y > sidewallTopY) {
|
||||
if (roofHeight <= 0 || y >= roofTipY) return 0;
|
||||
return binRadius * (1 - (y - sidewallTopY) / roofHeight);
|
||||
}
|
||||
// Straight sidewall
|
||||
if (y >= sidewallBaseY) return binRadius;
|
||||
// Inside the hopper cone — tapers from binRadius at sidewallBaseY to 0 at hopperTipY
|
||||
if (hopperHeight <= 0 || y <= hopperTipY) return 0;
|
||||
return binRadius * ((y - hopperTipY) / hopperHeight);
|
||||
};
|
||||
|
|
@ -192,7 +202,7 @@ export default function TempHeatMap({ binDimensions, targetTemp, nodes, flatMaxY
|
|||
const getSurfaceY = (x: number, z: number): number => {
|
||||
if (surfaceAnchors.length > 0) {
|
||||
const raw = idwSurfaceY(x, z, surfaceAnchors, SURFACE_IDW_POWER);
|
||||
return Math.max(grainFloorY, Math.min(sidewallHeight / 2, raw));
|
||||
return Math.max(grainFloorY, Math.min(roofTipY, raw));
|
||||
}
|
||||
return flatMaxY ?? sidewallBaseY;
|
||||
};
|
||||
|
|
@ -416,6 +426,8 @@ export default function TempHeatMap({ binDimensions, targetTemp, nodes, flatMaxY
|
|||
binRadius,
|
||||
upperThreshold,
|
||||
hopperHeight,
|
||||
roofHeight,
|
||||
roofTipY,
|
||||
flatMaxY,
|
||||
flatMinY,
|
||||
]);
|
||||
|
|
@ -455,5 +467,4 @@ export default function TempHeatMap({ binDimensions, targetTemp, nodes, flatMaxY
|
|||
</mesh>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import { ConeVolume, CylinderVolume } from "common/TrigFunctions";
|
||||
|
||||
/**
|
||||
* Y bounds of filled grain volume — matches GrainFillFlat / GrainCableFill fallback volume math.
|
||||
*/
|
||||
|
|
@ -6,28 +8,40 @@ export interface GrainYBounds {
|
|||
maxY: number;
|
||||
}
|
||||
|
||||
// Grain never physically fills all the way to the roof tip — cap roof fill
|
||||
// at this fraction of roofHeight so there is always visible breathing room.
|
||||
const MAX_ROOF_FILL_FRACTION = 0.75;
|
||||
|
||||
export function grainYBoundsFromFillPercent(
|
||||
diameter: number,
|
||||
sidewallHeight: number,
|
||||
hopperHeight: number,
|
||||
roofHeight: number,
|
||||
fillPercent: number
|
||||
): GrainYBounds {
|
||||
const radius = diameter / 2;
|
||||
const radius = diameter / 2;
|
||||
const sidewallBaseY = -sidewallHeight / 2;
|
||||
const hopperTipY = sidewallBaseY - hopperHeight;
|
||||
const hopperTipY = sidewallBaseY - hopperHeight;
|
||||
const roofBaseY = sidewallHeight / 2;
|
||||
|
||||
if (fillPercent <= 0) {
|
||||
return { minY: sidewallBaseY, maxY: sidewallBaseY };
|
||||
}
|
||||
|
||||
const cylinderVolume = Math.PI * radius * radius * sidewallHeight;
|
||||
const hopperVolume =
|
||||
hopperHeight > 0 ? (1 / 3) * Math.PI * radius * radius * hopperHeight : 0;
|
||||
const totalVolume = cylinderVolume + hopperVolume;
|
||||
const cylinderVolume = CylinderVolume(radius, sidewallHeight);
|
||||
const hopperVolume = hopperHeight > 0 ? ConeVolume(radius, hopperHeight) : 0;
|
||||
const roofVolume = roofHeight > 0 ? ConeVolume(radius, roofHeight) : 0;
|
||||
|
||||
// Volume of the usable roof portion (up to MAX_ROOF_FILL_FRACTION of height).
|
||||
// A cone fills as h³ so the capped volume fraction = MAX_ROOF_FILL_FRACTION³.
|
||||
const roofVolumeCapped = roofVolume * Math.pow(MAX_ROOF_FILL_FRACTION, 3);
|
||||
|
||||
const totalVolume = cylinderVolume + hopperVolume + roofVolumeCapped;
|
||||
const filledVolume = totalVolume * fillPercent;
|
||||
|
||||
// Fill the hopper first
|
||||
if (hopperHeight > 0 && filledVolume <= hopperVolume) {
|
||||
const ratio = filledVolume / hopperVolume;
|
||||
const ratio = filledVolume / hopperVolume;
|
||||
const hopperFillHeight = hopperHeight * Math.pow(ratio, 1 / 3);
|
||||
return {
|
||||
minY: hopperTipY,
|
||||
|
|
@ -35,15 +49,23 @@ export function grainYBoundsFromFillPercent(
|
|||
};
|
||||
}
|
||||
|
||||
const hopperFillHeight = hopperHeight;
|
||||
const remainingVolume = filledVolume - hopperVolume;
|
||||
const cylinderFillHeight = Math.max(
|
||||
0,
|
||||
remainingVolume / (Math.PI * radius * radius)
|
||||
);
|
||||
// Then fill the cylinder
|
||||
const volumeAfterHopper = filledVolume - hopperVolume;
|
||||
if (volumeAfterHopper <= cylinderVolume) {
|
||||
const cylinderFillHeight = volumeAfterHopper / (Math.PI * radius * radius);
|
||||
return {
|
||||
minY: hopperHeight > 0 ? hopperTipY : sidewallBaseY,
|
||||
maxY: sidewallBaseY + cylinderFillHeight,
|
||||
};
|
||||
}
|
||||
|
||||
// Finally fill into the roof cone, capped at MAX_ROOF_FILL_FRACTION
|
||||
const roofFilledVolume = volumeAfterHopper - cylinderVolume;
|
||||
const roofRatio = Math.min(1, roofFilledVolume / roofVolumeCapped);
|
||||
const roofFillHeight = roofHeight * MAX_ROOF_FILL_FRACTION * Math.pow(roofRatio, 1 / 3);
|
||||
|
||||
return {
|
||||
minY: hopperHeight > 0 ? hopperTipY : sidewallBaseY,
|
||||
maxY: sidewallBaseY + cylinderFillHeight,
|
||||
maxY: roofBaseY + roofFillHeight,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -513,8 +513,8 @@ const dateSelector = () => {
|
|||
<Typography textAlign={"center"}>{currentCheckpointTime ? currentCheckpointTime.format("MMM D, YYYY h:mm A") : moment().format("MMM D, YYYY h:mm A")}</Typography>
|
||||
<LinearProgress value={displayProgress} variant="determinate" />
|
||||
<Box display="flex" flexDirection="row" justifyContent="space-between">
|
||||
<Typography>{startDate.format("MMM D, YYYY")}</Typography>
|
||||
<Typography>{endDate.format("MMM D, YYYY")}</Typography>
|
||||
<Typography variant="caption">{startDate.format("MMM D, YYYY")}</Typography>
|
||||
<Typography variant="caption">{endDate.format("MMM D, YYYY")}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* date range selector */}
|
||||
|
|
|
|||
|
|
@ -81,8 +81,8 @@ export default function BinTableExpanded(props: Props) {
|
|||
const inGrain = nodeIndex < cable.topNode
|
||||
|
||||
if (inGrain) {
|
||||
if (raw > targetTemp + 5) color = warning
|
||||
else if (raw > targetTemp) color = caution
|
||||
if (raw > targetTemp + 10) color = warning
|
||||
else if (raw > targetTemp + 5) color = caution
|
||||
else color = inBounds
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ export default function BinTableExpanded(props: Props) {
|
|||
const inGrain = nodeIndex < cable.topNode
|
||||
if (inGrain) {
|
||||
if (raw > targetMoisture + 1.5) color = warning
|
||||
else if (raw > targetMoisture) color = caution
|
||||
else if (raw > targetMoisture + 0.5) color = caution
|
||||
else color = inBounds
|
||||
}
|
||||
return { display: raw.toFixed(2) + "%", color }
|
||||
|
|
@ -196,15 +196,15 @@ export default function BinTableExpanded(props: Props) {
|
|||
<Box display="flex" alignItems="center" justifyContent={"space-between"} marginY={2}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Box sx={{ width: 40, height: 20, backgroundColor: inBounds, borderRadius: 1, flexShrink: 0 }} />
|
||||
<Typography sx={{fontWeight: 600, fontSize: 10}}>On or below target</Typography>
|
||||
<Typography sx={{fontWeight: 600, fontSize: 10}}>On target</Typography>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Box sx={{ width: 40, height: 20, backgroundColor: caution, borderRadius: 1, flexShrink: 0 }} />
|
||||
<Typography sx={{fontWeight: 600, fontSize: 10}}>{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "+10 °F" : "+5 °C"} above target</Typography>
|
||||
<Typography sx={{fontWeight: 600, fontSize: 10}}>{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "~10 °F" : "+5 °C"} above</Typography>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Box sx={{ width: 40, height: 20, backgroundColor: warning, borderRadius: 1, flexShrink: 0 }} />
|
||||
<Typography sx={{fontWeight: 600, fontSize: 10}}>{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "+20 °F" : "+10 °C"} above target</Typography>
|
||||
<Typography sx={{fontWeight: 600, fontSize: 10}}>{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "~20 °F" : "+10 °C"} above</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", flexDirection: "row", overflowX: "auto", gap: 2, paddingBottom: 2}}>
|
||||
|
|
|
|||
|
|
@ -390,6 +390,7 @@ export default function BinSummary(props: Props){
|
|||
<ChangeGrainDialog bin={bin} open={openGrainDialog} closeDialog={()=>setOpenGrainDialog(false)} permissions={permissions} updateBin={updateBinCallback}/>
|
||||
{loading ? <Skeleton variant="rectangular" height={100} sx={{marginBottom: 2}} /> : isMobile ? inventorySummaryMobile() : inventorySummaryDesktop()}
|
||||
<Grid2 container spacing={2}>
|
||||
{!isMobile &&
|
||||
<Grid2 size={isMobile ? 12 : 7}>
|
||||
{loading ?
|
||||
<Skeleton variant="rectangular" height={600}/>
|
||||
|
|
@ -406,8 +407,7 @@ export default function BinSummary(props: Props){
|
|||
binPrefs={binPrefs}
|
||||
updateBinCallback={updateBinCallback}
|
||||
/>
|
||||
{/* only show the player on desktop */}
|
||||
{setBin && !isMobile &&
|
||||
{setBin &&
|
||||
<Box paddingX={2} position="absolute" bottom={10} width="100%">
|
||||
<BinPlayer bin={bin} componentDevices={componentDevices} setBin={setBin}/>
|
||||
</Box>
|
||||
|
|
@ -415,6 +415,7 @@ export default function BinSummary(props: Props){
|
|||
</Card>
|
||||
}
|
||||
</Grid2>
|
||||
}
|
||||
<Grid2 size={isMobile ? 12 : 5}>
|
||||
{loading ?
|
||||
<Skeleton variant="rectangular" height={600}/>
|
||||
|
|
@ -428,7 +429,12 @@ export default function BinSummary(props: Props){
|
|||
linkedComponents={componentMap}
|
||||
permissions={permissions}
|
||||
cables={cables}
|
||||
plenums={plenums}/>
|
||||
plenums={plenums}
|
||||
fans={fans}
|
||||
heaters={heaters}
|
||||
binPrefs={binPrefs}
|
||||
componentMap={componentMap}
|
||||
setBin={setBin}/>
|
||||
</Card>
|
||||
}
|
||||
</Grid2>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ import BinAnalysisGraphs from "./binAnalysisGraphs";
|
|||
import { GrainCable } from "models/GrainCable";
|
||||
import { Plenum } from "models/Plenum";
|
||||
import { Controller } from "models/Controller";
|
||||
import { useMobile } from "hooks";
|
||||
import Bin3dVisualizer from "bin/bin3dVisualizer";
|
||||
import BinPlayer from "bin/BinPlayer";
|
||||
|
||||
interface Props {
|
||||
bin: Bin
|
||||
|
|
@ -21,6 +24,12 @@ interface Props {
|
|||
permissions: pond.Permission[]
|
||||
cables: GrainCable[]
|
||||
plenums: Plenum[]
|
||||
showBinTab?: boolean
|
||||
fans?: Controller[]
|
||||
heaters?: Controller[]
|
||||
componentMap: Map<string, Component>
|
||||
binPrefs?: Map<string, pond.BinComponentPreferences>
|
||||
setBin?: React.Dispatch<React.SetStateAction<Bin>>;
|
||||
}
|
||||
|
||||
interface TabPanelProps {
|
||||
|
|
@ -51,8 +60,9 @@ function TabPanelMine(props: TabPanelProps) {
|
|||
}
|
||||
|
||||
export default function BinDetails (props: Props) {
|
||||
const {bin, updateBinCallback, linkedComponents, componentDevices, devices, permissions, cables, plenums} = props
|
||||
const [currentTab, setCurrentTab] = useState(0)
|
||||
const {bin, updateBinCallback, linkedComponents, componentDevices, devices, permissions, cables, plenums, showBinTab, fans, heaters, componentMap, binPrefs, setBin} = props
|
||||
const isMobile = useMobile()
|
||||
const [currentTab, setCurrentTab] = useState((isMobile || showBinTab) ? "bin" : "table")
|
||||
|
||||
//this is where we will have the tabs and tab panels for the table view, alerts, controls, and analysis
|
||||
|
||||
|
|
@ -65,22 +75,48 @@ export default function BinDetails (props: Props) {
|
|||
textColor="primary"
|
||||
variant="fullWidth"
|
||||
sx={{ flexShrink: 0 }}>
|
||||
<Tab label={"Table View"} value={0} />
|
||||
<Tab label={"Alerts"} value={1} />
|
||||
<Tab label={"Controls"} value={2} />
|
||||
<Tab label={"Analysis"} value={3} />
|
||||
{isMobile && <Tab label={"Bin"} value={"bin"} />}
|
||||
<Tab label={"Table"} value={"table"} />
|
||||
<Tab label={"Alerts"} value={"alerts"} />
|
||||
<Tab label={"Controls"} value={"controls"} />
|
||||
<Tab label={"Analysis"} value={"analysis"} />
|
||||
</Tabs>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", flex: 1, minHeight: 0, overflow: "hidden" }}>
|
||||
<TabPanelMine value={currentTab} index={0}>
|
||||
{(isMobile || showBinTab) &&
|
||||
<TabPanelMine value={currentTab} index={"bin"}>
|
||||
<Bin3dVisualizer
|
||||
bin={bin}
|
||||
fans={fans}
|
||||
heaters={heaters}
|
||||
permissions={permissions}
|
||||
devices={devices}
|
||||
componentDevices={componentDevices}
|
||||
componentMap={componentMap}
|
||||
binPrefs={binPrefs}
|
||||
updateBinCallback={updateBinCallback}
|
||||
/>
|
||||
{setBin &&
|
||||
<Box padding={1}>
|
||||
<BinPlayer bin={bin} componentDevices={componentDevices} setBin={setBin}/>
|
||||
</Box>
|
||||
}
|
||||
</TabPanelMine>
|
||||
}
|
||||
<TabPanelMine value={currentTab} index={"table"}>
|
||||
<BinTableView bin={bin} updateBinCallback={updateBinCallback}/>
|
||||
{setBin && isMobile &&
|
||||
<Box padding={1}>
|
||||
<BinPlayer bin={bin} componentDevices={componentDevices} setBin={setBin}/>
|
||||
</Box>
|
||||
}
|
||||
</TabPanelMine>
|
||||
<TabPanelMine value={currentTab} index={1}>
|
||||
<TabPanelMine value={currentTab} index={"alerts"}>
|
||||
<BinAlerts componentDevices={componentDevices} linkedComponents={linkedComponents} permissions={permissions}/>
|
||||
</TabPanelMine>
|
||||
<TabPanelMine value={currentTab} index={2}>
|
||||
<TabPanelMine value={currentTab} index={"controls"}>
|
||||
<BinControls componentDevices={componentDevices} devices={devices} linkedComponents={linkedComponents} permissions={permissions}/>
|
||||
</TabPanelMine>
|
||||
<TabPanelMine value={currentTab} index={3}>
|
||||
<TabPanelMine value={currentTab} index={"analysis"}>
|
||||
<BinAnalysisGraphs bin={bin} cables={cables} plenums={plenums} componentDevices={componentDevices}/>
|
||||
</TabPanelMine>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ const useStyles = makeStyles((theme: Theme) => ({
|
|||
headerCellStyle: {
|
||||
fontWeight: 650,
|
||||
backgroundColor: darken(theme.palette.background.paper, 0.2),
|
||||
fontSize: 15,
|
||||
textAlign: "center",
|
||||
borderTop: "1px solid black",
|
||||
borderLeft: "1px solid black",
|
||||
|
|
@ -32,6 +31,18 @@ const useStyles = makeStyles((theme: Theme) => ({
|
|||
borderRight: "1px solid black",
|
||||
}
|
||||
},
|
||||
mobileHeaderCellStyle: {
|
||||
fontWeight: 650,
|
||||
backgroundColor: darken(theme.palette.background.paper, 0.2),
|
||||
textAlign: "center",
|
||||
borderTop: "1px solid black",
|
||||
borderLeft: "1px solid black",
|
||||
borderBottom: "1px solid black",
|
||||
"&:last-child": {
|
||||
borderRight: "1px solid black",
|
||||
},
|
||||
padding: 2
|
||||
},
|
||||
cellStyle: {
|
||||
padding: 2,
|
||||
fontWeight: 650,
|
||||
|
|
@ -164,9 +175,9 @@ export default function BinTableView(props: Props) {
|
|||
if(level.grainTemps.length > 0){
|
||||
lvlTemp = avg(level.grainTemps)
|
||||
//if the average temp is 5 degrees above the bins threshold
|
||||
if(lvlTemp > (bin.targetTemp() + 5)){
|
||||
if(lvlTemp > (bin.targetTemp() + 10)){
|
||||
tempColour = warning
|
||||
}else if(lvlTemp > bin.targetTemp()){
|
||||
}else if(lvlTemp > bin.targetTemp() + 5){
|
||||
//if the average temp is greater than 5 degrees over
|
||||
tempColour = caution
|
||||
}else {
|
||||
|
|
@ -179,7 +190,7 @@ export default function BinTableView(props: Props) {
|
|||
if(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
|
||||
lvlTemp = celsiusToFahrenheit(lvlTemp)
|
||||
}
|
||||
tempDisplay = lvlTemp.toFixed(2) + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")
|
||||
tempDisplay = lvlTemp.toFixed(2)
|
||||
}
|
||||
|
||||
//determine the moisture to show
|
||||
|
|
@ -187,8 +198,7 @@ export default function BinTableView(props: Props) {
|
|||
lvlMoisture = avg(level.grainMoistures)
|
||||
if(lvlMoisture > (bin.targetMoisture() + 1.5)){
|
||||
moistureColour = warning
|
||||
}else if(lvlMoisture > bin.targetMoisture()){
|
||||
//if the average temp is greater than 5 degrees over
|
||||
}else if(lvlMoisture > bin.targetMoisture() + 0.5){
|
||||
moistureColour = caution
|
||||
}else {
|
||||
moistureColour = inBounds
|
||||
|
|
@ -222,11 +232,11 @@ export default function BinTableView(props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
const tempThreshDisplay = () => {
|
||||
const tempThreshDisplay = (hideUnit?: boolean) => {
|
||||
if(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
|
||||
return tempTarget.toFixed(2) + " °F"
|
||||
return tempTarget.toFixed(2) + (hideUnit ? "" : " °F")
|
||||
}
|
||||
return tempTarget.toFixed(2) + " °C"
|
||||
return tempTarget.toFixed(2) + (hideUnit ? "" : " °C")
|
||||
}
|
||||
|
||||
const LegendSwatch = ({ color }: { color: string }) => (
|
||||
|
|
@ -287,7 +297,7 @@ export default function BinTableView(props: Props) {
|
|||
|
||||
|
||||
return (
|
||||
<Box padding={2} sx={{ display: "flex", flexDirection: "column", height: "100%" }}>
|
||||
<Box padding={2} sx={{ display: "flex", flexDirection: "column", height: isMobile ? "88%" : "100%" }}>
|
||||
{expandedTableDialog()}
|
||||
{/* Level Table */}
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
|
|
@ -311,80 +321,80 @@ export default function BinTableView(props: Props) {
|
|||
<Box sx={{ overflowY: "auto", flex: 1, minHeight: 0 }}>
|
||||
<Table className={classes.tableStyle}>
|
||||
<TableHead>
|
||||
<TableRow ref={headerRow1Ref}>
|
||||
<TableCell className={classes.headerCellStyle} sx={{ top: 0, position: "sticky", zIndex: 3 }}>
|
||||
<Box justifyContent={"center"} sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<GppGood />
|
||||
<Typography fontWeight={650}>
|
||||
Targets
|
||||
</Typography>
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell className={classes.headerCellStyle} sx={{ top: 0, position: "sticky", zIndex: 3 }}>
|
||||
<Box justifyContent={"center"} sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<TemperatureIcon heightWidth={30}/>
|
||||
{enterTemp ?
|
||||
updateTempEntry()
|
||||
:
|
||||
<Typography fontWeight={650}>
|
||||
{tempThreshDisplay()}
|
||||
</Typography>
|
||||
}
|
||||
{enterTemp ?
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setEnterTemp(false)
|
||||
//and update the bin with the new temp target
|
||||
update()
|
||||
}}
|
||||
>
|
||||
<Save />
|
||||
</IconButton>
|
||||
:
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setEnterTemp(true)
|
||||
}}>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell className={classes.headerCellStyle} sx={{ top: 0, position: "sticky", zIndex: 3 }}>
|
||||
<Box justifyContent={"center"} sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<HumidityIcon height={30} width={20}/>
|
||||
{enterMoisture ?
|
||||
updateMoistureEntry()
|
||||
:
|
||||
<Typography fontWeight={650} sx={{marginLeft: "10px"}}>
|
||||
{bin.targetMoisture()}%
|
||||
</Typography>
|
||||
}
|
||||
{enterMoisture ?
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setEnterMoisture(false)
|
||||
//and update the bin with the new temp target
|
||||
update()
|
||||
}}
|
||||
>
|
||||
<Save />
|
||||
</IconButton>
|
||||
:
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setEnterMoisture(true)
|
||||
}}>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
}
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow ref={headerRow1Ref}>
|
||||
{/* "Targets" cell — hide on mobile when either field is being edited */}
|
||||
{!(isMobile && (enterTemp || enterMoisture)) && (
|
||||
<TableCell className={isMobile ? classes.mobileHeaderCellStyle : classes.headerCellStyle} sx={{top: 0, position: "sticky", zIndex: 3 }}>
|
||||
<Box justifyContent={"center"} sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<GppGood />
|
||||
{!isMobile &&
|
||||
<Typography fontSize={16} fontWeight={650}>
|
||||
Targets
|
||||
</Typography>
|
||||
}
|
||||
</Box>
|
||||
</TableCell>
|
||||
)}
|
||||
|
||||
{/* Temperature cell */}
|
||||
{!(isMobile && enterMoisture) && (
|
||||
<TableCell
|
||||
className={isMobile ? classes.mobileHeaderCellStyle : classes.headerCellStyle}
|
||||
colSpan={isMobile && enterTemp ? 3 : 1}
|
||||
sx={{ top: 0, position: "sticky", zIndex: 3 }}
|
||||
>
|
||||
<Box justifyContent={"center"} sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<TemperatureIcon heightWidth={isMobile ? 25 : 30} />
|
||||
{enterTemp ? updateTempEntry() : (
|
||||
<Typography fontSize={isMobile ? 12 : 16} fontWeight={650}>
|
||||
{tempThreshDisplay(isMobile)}
|
||||
</Typography>
|
||||
)}
|
||||
{enterTemp ? (
|
||||
<IconButton onClick={() => { setEnterTemp(false); update(); }}>
|
||||
<Save />
|
||||
</IconButton>
|
||||
) : (
|
||||
<IconButton onClick={() => setEnterTemp(true)}>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
</TableCell>
|
||||
)}
|
||||
{/* Moisture cell — hide on mobile when temperature is being edited */}
|
||||
{!(isMobile && enterTemp) && (
|
||||
<TableCell
|
||||
className={isMobile ? classes.mobileHeaderCellStyle : classes.headerCellStyle}
|
||||
colSpan={isMobile && enterMoisture ? 3 : 1}
|
||||
sx={{ top: 0, position: "sticky", zIndex: 3 }}
|
||||
>
|
||||
<Box justifyContent={"center"} sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<HumidityIcon height={isMobile ? 25 : 30} width={20} />
|
||||
{enterMoisture ? updateMoistureEntry() : (
|
||||
<Typography fontSize={isMobile ? 12 : 16} fontWeight={650} sx={{ marginLeft: "10px" }}>
|
||||
{bin.targetMoisture()}%
|
||||
</Typography>
|
||||
)}
|
||||
{enterMoisture ? (
|
||||
<IconButton onClick={() => { setEnterMoisture(false); update(); }}>
|
||||
<Save />
|
||||
</IconButton>
|
||||
) : (
|
||||
<IconButton onClick={() => setEnterMoisture(true)}>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className={classes.headerCellStyle} sx={{ top: firstRowHeight, position: "sticky", zIndex: 3 }}>Level</TableCell>
|
||||
<TableCell className={classes.headerCellStyle} sx={{ top: firstRowHeight, position: "sticky", zIndex: 3 }}>Temperature</TableCell>
|
||||
<TableCell className={classes.headerCellStyle} sx={{ top: firstRowHeight, position: "sticky", zIndex: 3 }}>Moisture</TableCell>
|
||||
<TableCell className={isMobile ? classes.mobileHeaderCellStyle : classes.headerCellStyle} sx={{ top: firstRowHeight, position: "sticky", zIndex: 3 }}>Level</TableCell>
|
||||
<TableCell className={isMobile ? classes.mobileHeaderCellStyle : classes.headerCellStyle} sx={{ top: firstRowHeight, position: "sticky", zIndex: 3 }}>
|
||||
{isMobile ? "T" : "Temperature"}{(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " (°F)" : " (°C)")}
|
||||
</TableCell>
|
||||
<TableCell className={isMobile ? classes.mobileHeaderCellStyle : classes.headerCellStyle} sx={{ top: firstRowHeight, position: "sticky", zIndex: 3 }}>EMC (%)</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
|
|
@ -402,18 +412,17 @@ export default function BinTableView(props: Props) {
|
|||
<Box display="flex" alignItems="center" justifyContent="space-between" marginTop={2} sx={{ flexShrink: 0 }}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<LegendSwatch color={inBounds} />
|
||||
<Typography sx={{fontWeight: 600, fontSize: 10}}>On or below target</Typography>
|
||||
<Typography sx={{fontWeight: 600, fontSize: 10}}>On target</Typography>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<LegendSwatch color={caution} />
|
||||
<Typography sx={{fontWeight: 600, fontSize: 10}}>{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "+10 °F" : "+5 °C"} above target</Typography>
|
||||
<Typography sx={{fontWeight: 600, fontSize: 10}}>{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "~10 °F" : "+5 °C"} (0.5%) above</Typography>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<LegendSwatch color={warning} />
|
||||
<Typography sx={{fontWeight: 600, fontSize: 10}}>{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "+20 °F" : "+10 °C"} above target</Typography>
|
||||
<Typography sx={{fontWeight: 600, fontSize: 10}}>{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "~20 °F" : "+10 °C"} (1.5%) above</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue