added heaters page (still need to fix table) fixed the heater card on the site page

This commit is contained in:
csawatzky 2025-04-03 15:19:16 -06:00
parent 5065c07820
commit 43226717c1
7 changed files with 808 additions and 45 deletions

View file

@ -0,0 +1,58 @@
import { Fab, Theme } from "@mui/material";
import { makeStyles } from "@mui/styles";
import Icon from "assets/components/heaterDark.png";
import { ImgIcon } from "common/ImgIcon";
import { useMobile } from "hooks";
interface Props {
onClick: () => void;
pulse: boolean;
}
const useStyles = makeStyles((theme: Theme) => ({
"@keyframes pulsate": {
to: {
boxShadow: "0 0 0 16px" + theme.palette.primary.main + "00"
}
},
fab: {
zIndex: 2,
background: theme.palette.primary.main,
"&:hover": {
background: theme.palette.primary.main
},
"&:focus": {
background: theme.palette.primary.main
},
position: "fixed",
bottom: theme.spacing(8), //for mobile navigator
right: theme.spacing(2),
[theme.breakpoints.up("sm")]: {
bottom: theme.spacing(2)
}
},
pulse: {
zIndex: 2,
boxShadow: "0 0 0 0 " + theme.palette.primary.main + "75",
animation: "$pulsate 1.75s infinite cubic-bezier(0.66, 0.33, 0, 1)"
}
}));
export default function AddObjectHeaterFab(props: Props) {
const { onClick, pulse } = props;
const classes = useStyles();
const isMobile = useMobile();
const pulseString = pulse ? classes.pulse : "";
const classString = classes.fab + " " + pulseString;
return (
<Fab
onClick={onClick}
aria-label="Add New Heater"
className={classString}
size={isMobile ? "medium" : "large"}>
<ImgIcon alt="Add New Heater" src={Icon} />
</Fab>
);
}

View file

@ -9,7 +9,7 @@ import {
ListItemText,
Theme,
Typography,
useTheme
//useTheme
} from "@mui/material";
import { GetDefaultDateRange } from "common/time/DateRange";
import UnitMeasurementSummary from "component/UnitMeasurementSummary";
@ -79,7 +79,7 @@ export default function ObjectHeaterCard(props: Props) {
const [allComponentsRecent, setAllComponentsRecent] = useState(true);
//const history = useHistory();
const navigate = useNavigate();
const theme = useTheme();
//const theme = useTheme();
useEffect(() => {
let heater = ObjectHeater.any(heaterData.heater);
@ -237,7 +237,7 @@ export default function ObjectHeaterCard(props: Props) {
if (!lastFuelMutation) {
return (
<Grid container direction="row" wrap="nowrap" alignContent="center" alignItems="center">
{/* <Grid item>
<Grid>
<Avatar
variant="square"
alt="icon"
@ -245,7 +245,7 @@ export default function ObjectHeaterCard(props: Props) {
style={{ float: "left" }}
className={classes.avatarIcon}
/>
</Grid> */}
</Grid>
<Grid style={{ paddingLeft: 5 }}>
N/A
</Grid>
@ -295,42 +295,11 @@ export default function ObjectHeaterCard(props: Props) {
);
};
//content of the card of there is no device linked to the heater
const unlinkedCard = () => {
return (
<React.Fragment>
<CardContent style={{ paddingLeft: 30, paddingRight: 30 }}>
<Grid
container
direction="row"
alignContent="center"
alignItems="center">
<Grid size={4}>
<EventBlocker>
<Button
variant="contained"
onClick={() => {
linkDevice(heater, heaterData.devices);
}}>
<Link />
</Button>
</EventBlocker>
</Grid>
<Grid size={7}>
<Typography>{heater.name}</Typography>
</Grid>
<Grid size={1}>
<ObjectHeaterIcon />
</Grid>
</Grid>
</CardContent>
</React.Fragment>
);
};
//content of the card if a device is linked to the heater
const linkedCard = (device: Device) => {
const productIcon = GetDeviceProductIcon(device);
const cardBody = (device?: Device) => {
const productIcon = GetDeviceProductIcon(device ?? Device.create());
if(device){
return (
<React.Fragment>
<Box height="100%" display="flex" alignItems="center" flexWrap="nowrap" padding={2}>
@ -382,10 +351,10 @@ export default function ObjectHeaterCard(props: Props) {
direction="row"
alignContent="center"
alignItems="center">
<Grid>{componentDisplay(heaterComponent)}</Grid>
<Grid>{componentDisplay(heaterTempComponent)}</Grid>
<Grid>{componentDisplay(siteTempComponent)}</Grid>
<Grid>{fuelMutationDisplay()}</Grid>
<Grid size={3}>{componentDisplay(heaterComponent)}</Grid>
<Grid size={3}>{componentDisplay(heaterTempComponent)}</Grid>
<Grid size={3}>{componentDisplay(siteTempComponent)}</Grid>
<Grid size={3}>{fuelMutationDisplay()}</Grid>
</Grid>
</CardContent>
) : (
@ -399,11 +368,42 @@ export default function ObjectHeaterCard(props: Props) {
)}
</React.Fragment>
);
}else {
return (
<React.Fragment>
<CardContent style={{ paddingLeft: 30, paddingRight: 30 }}>
<Grid
container
direction="row"
alignContent="center"
alignItems="center">
<Grid size={4}>
<EventBlocker>
<Button
variant="contained"
onClick={() => {
linkDevice(heater, heaterData.devices);
}}>
<Link />
</Button>
</EventBlocker>
</Grid>
<Grid size={7}>
<Typography>{heater.name}</Typography>
</Grid>
<Grid size={1}>
<ObjectHeaterIcon />
</Grid>
</Grid>
</CardContent>
</React.Fragment>
)
}
};
return (
<Card raised onClick={goToHeater} style={{ margin: 5 }}>
{connectedDevice ? linkedCard(connectedDevice) : unlinkedCard()}
{cardBody(connectedDevice)}
</Card>
);
}

View file

@ -0,0 +1,464 @@
import {
Button,
DialogActions,
DialogContent,
DialogTitle,
Grid2 as Grid,
InputAdornment,
List,
ListItem,
MenuItem,
Tab,
Tabs,
TextField,
Tooltip,
Typography
} from "@mui/material";
import ResponsiveDialog from "common/ResponsiveDialog";
import { Component, Site } from "models";
import { ObjectHeater } from "models/ObjectHeater";
import { useGlobalState, useObjectHeaterAPI, useSiteAPI } from "providers";
import { useSnackbar } from "hooks";
import React, { useEffect, useState } from "react";
import { pond } from "protobuf-ts/pond";
import LinearMutationBuilder from "common/LinearMutationBuilder";
import { makeStyles } from "@mui/styles";
interface TabPanelProps {
children?: React.ReactNode;
index: any;
value: any;
}
function TabPanelMine(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
aria-labelledby={`simple-tab-${index}`}
{...other}>
{value === index && <React.Fragment>{children}</React.Fragment>}
</div>
);
}
const useStyles = makeStyles(() => ({
dialogContent: {
minHeight: "25vh"
},
tabSmall: {
width: "100%",
boxSizing: "border-box",
flexShrink: 1,
minWidth: "48px",
marginTop: 0,
paddingTop: 0
}
})
);
interface Props {
open: boolean;
componentsByDevice?: Map<string, Component[]>;
close: (heater?: ObjectHeater) => void;
heater?: ObjectHeater;
sites?: Site[];
}
export default function ObjectHeaterSettings(props: Props) {
const { open, close, heater, sites, componentsByDevice } = props;
const objectHeaterAPI = useObjectHeaterAPI();
const siteAPI = useSiteAPI();
const [{ as }] = useGlobalState();
const classes = useStyles();
const [siteOptions, setSiteOptions] = useState<Site[]>([]);
const [heaterName, setHeaterName] = useState("");
const [heaterMake, setHeaterMake] = useState("");
const [heaterModel, setHeaterModel] = useState("");
const [siteKey, setSiteKey] = useState("");
const [fuelType, setFuelType] = useState<pond.FuelType>(pond.FuelType.FUEL_TYPE_UNKNOWN);
const [tankSize, setTankSize] = useState(0);
const [fuelConsumption, setFuelConsumption] = useState(0);
const [airCirculation, setAirCirculation] = useState(0);
const { openSnack } = useSnackbar();
const [currentTab, setCurrentTab] = useState(0);
const [linearMutations, setLinearMutations] = useState<pond.LinearMutation[]>([]);
const [newMutationDialog, setNewMutationDialog] = useState(false);
const [existingMutation, setExistingMutation] = useState<pond.LinearMutation>();
const [removeDialog, setRemoveDialog] = useState(false);
const closeDialog = (heater?: ObjectHeater) => {
close(heater);
};
//load the sites if not passed in
useEffect(() => {
if (sites) {
setSiteOptions(sites);
} else {
siteAPI.listSites(0, 0, undefined, undefined, undefined, as).then(resp => {
setSiteOptions(resp.data.sites.map(s => Site.any(s)));
});
}
}, [sites, as, siteAPI]);
//set the state variables
useEffect(() => {
setHeaterName("");
setHeaterMake("");
setHeaterModel("");
setSiteKey("");
setFuelType(pond.FuelType.FUEL_TYPE_UNKNOWN);
setFuelConsumption(0);
setTankSize(0);
setAirCirculation(0);
if (heater) {
setHeaterName(heater.name);
setHeaterMake(heater.settings.make);
setHeaterModel(heater.settings.model);
setSiteKey(heater.settings.siteKey);
setFuelType(heater.settings.fuelType);
setFuelConsumption(heater.settings.fuelConsumption);
setTankSize(heater.settings.tankSize);
setAirCirculation(heater.settings.airCirculation);
setLinearMutations(heater.settings.mutations);
}
}, [heater]);
const confirm = () => {
if (heater) {
let settings = heater.settings;
settings.make = heaterMake;
settings.model = heaterModel;
settings.siteKey = siteKey;
settings.tankSize = tankSize;
settings.fuelType = fuelType;
settings.fuelConsumption = fuelConsumption;
settings.airCirculation = airCirculation;
settings.mutations = linearMutations;
objectHeaterAPI
.updateObjectHeater(heater.key, heaterName, settings)
.then(resp => {
openSnack("Heater update");
let updatedHeater = heater;
updatedHeater.name = heaterName;
updatedHeater.settings = settings;
closeDialog(updatedHeater);
})
.catch(err => {
openSnack("Failed to update heater");
});
} else {
let settings: pond.ObjectHeaterSettings = pond.ObjectHeaterSettings.create({
make: heaterMake,
model: heaterModel,
siteKey: siteKey,
fuelType: fuelType,
tankSize: tankSize,
fuelConsumption: fuelConsumption,
airCirculation: airCirculation,
mutations: linearMutations
});
objectHeaterAPI
.addObjectHeater(heaterName, settings)
.then(resp => {
openSnack("New heater added");
let newHeater = ObjectHeater.create(
pond.ObjectHeater.create({ key: resp.data.key, name: heaterName, settings: settings })
);
closeDialog(newHeater);
})
.catch(err => {
openSnack("There was a problem adding your heater");
});
}
};
const removeHeater = () => {
if (heater) {
objectHeaterAPI
.removeObjectHeater(heater.key)
.then(resp => {
openSnack("Heater Removed");
setRemoveDialog(false);
closeDialog();
})
.catch(err => {
openSnack("Failed to remove heater");
});
}
};
const settingsContent = () => {
return (
<React.Fragment>
<TextField
label="Heater Name"
fullWidth
value={heaterName}
onChange={e => {
setHeaterName(e.target.value);
}}
/>
<TextField
label="Site"
fullWidth
select
value={siteKey}
onChange={e => {
setSiteKey(e.target.value);
}}>
{siteOptions.map(site => (
<MenuItem key={site.key()} value={site.key()}>
{site.siteName()}
</MenuItem>
))}
</TextField>
<TextField
label="Make"
fullWidth
value={heaterMake}
onChange={e => {
setHeaterMake(e.target.value);
}}
/>
<TextField
label="Model"
fullWidth
value={heaterModel}
onChange={e => {
setHeaterModel(e.target.value);
}}
/>
<TextField
label="Fuel Type"
fullWidth
select
value={fuelType}
onChange={e => {
setFuelType(+e.target.value as pond.FuelType);
}}>
<MenuItem key="unknown" value={pond.FuelType.FUEL_TYPE_UNKNOWN}>
Select Fuel Type
</MenuItem>
<MenuItem key="gas" value={pond.FuelType.FUEL_TYPE_GASOLINE}>
Gasoline
</MenuItem>
<MenuItem key="diesel" value={pond.FuelType.FUEL_TYPE_DIESEL}>
Diesel
</MenuItem>
<MenuItem key="propane" value={pond.FuelType.FUEL_TYPE_PROPANE}>
Propane
</MenuItem>
</TextField>
<TextField
label="Tank Size"
fullWidth
type="number"
value={tankSize}
onChange={e => {
setTankSize(+e.target.value);
}}
InputProps={{
endAdornment: <InputAdornment position="end">G</InputAdornment>
}}
/>
<TextField
label="Fuel Consumption"
fullWidth
type="number"
value={fuelConsumption}
onChange={e => {
setFuelConsumption(+e.target.value);
}}
InputProps={{
endAdornment: <InputAdornment position="end">G/hr</InputAdornment>
}}
/>
<TextField
label="Air Circulation"
fullWidth
type="number"
value={airCirculation}
onChange={e => {
setAirCirculation(+e.target.value);
}}
InputProps={{
endAdornment: <InputAdornment position="end">CFM</InputAdornment>
}}
/>
</React.Fragment>
);
};
const mutationsContent = () => {
let mutations: JSX.Element[] = [];
if (heater && heater.settings.mutations) {
heater.settings.mutations.forEach((mut, i) => {
mutations.push(
<ListItem key={"mutation" + i}>
<Grid
container
direction="row"
alignContent="center"
alignItems="center">
<Grid size={5}>
<Typography>{mut.mutationName}</Typography>
</Grid>
<Grid>
<Button
variant="contained"
style={{ backgroundColor: "red" }}
onClick={() => {
let lm = linearMutations;
lm.splice(i, 1);
setLinearMutations([...lm]);
}}>
Remove
</Button>
<Button
style={{ marginLeft: 10 }}
variant="contained"
color="primary"
onClick={() => {
setExistingMutation(mut);
setNewMutationDialog(true);
}}>
Edit
</Button>
</Grid>
</Grid>
</ListItem>
);
});
}
let mutationList: JSX.Element = <List>{mutations}</List>;
return (
<React.Fragment>
<Button
variant="contained"
color="primary"
onClick={() => {
setExistingMutation(undefined);
setNewMutationDialog(true);
}}>
Add New Mutation
</Button>
<List>{mutationList}</List>
</React.Fragment>
);
};
const removeConfirmation = () => {
return (
<ResponsiveDialog open={removeDialog} onClose={() => setRemoveDialog(false)}>
<DialogContent>Are you sure you would like to delete heater?</DialogContent>
<DialogActions>
<Button
variant="contained"
color="primary"
onClick={() => {
setRemoveDialog(false);
}}>
Cancel
</Button>
<Button
variant="contained"
color="primary"
onClick={() => {
removeHeater();
}}>
Delete
</Button>
</DialogActions>
</ResponsiveDialog>
);
};
return (
<React.Fragment>
{removeConfirmation()}
<ResponsiveDialog
open={open}
onClose={() => {
closeDialog();
}}>
<DialogTitle>Heater Settings</DialogTitle>
<DialogContent>
<Tabs
value={currentTab}
onChange={(_, value) => setCurrentTab(value)}
indicatorColor="primary"
textColor="primary"
variant="fullWidth"
aria-label="device tabs"
classes={{ root: classes.tabSmall }}>
<Tab label={"Heater Settings"} className={classes.tabSmall} />
<Tab
style={{ pointerEvents: "auto" }}
label={
componentsByDevice ? (
"Calculated Measurements"
) : (
<Tooltip title="Requires Connected Device">
<span>Calculated Measurements</span>
</Tooltip>
)
}
className={classes.tabSmall}
disabled={!componentsByDevice}
/>
</Tabs>
<TabPanelMine value={currentTab} index={0}>
{settingsContent()}
</TabPanelMine>
<TabPanelMine value={currentTab} index={1}>
{mutationsContent()}
</TabPanelMine>
</DialogContent>
<DialogActions>
<Grid container direction="row">
<Grid>
{heater && (
<Button
variant="contained"
style={{ backgroundColor: "red" }}
onClick={() => {
setRemoveDialog(true);
}}>
Delete
</Button>
)}
</Grid>
<Grid></Grid>
</Grid>
<Button
variant="contained"
disabled={heaterName === ""}
color="primary"
onClick={() => {
confirm();
}}>
{heater ? "Update" : "Create"}
</Button>
</DialogActions>
{componentsByDevice && (
<LinearMutationBuilder
existingMutation={existingMutation}
componentsByDevice={componentsByDevice}
open={newMutationDialog}
onClose={() => {
setNewMutationDialog(false);
}}
onSubmit={newMutation => {
let lm = linearMutations;
lm.push(newMutation);
setLinearMutations(lm);
}}
/>
)}
</ResponsiveDialog>
</React.Fragment>
);
}