frontend/src/device/presets/devicePresetCard.tsx

517 lines
17 KiB
TypeScript

import {
Box,
Button,
Card,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Grid2 as Grid,
InputAdornment,
MenuItem,
Slider,
TextField,
Typography
} from "@mui/material";
import { useMobile } from "hooks";
import { cloneDeep } from "lodash";
import { DevicePreset } from "models/DevicePreset";
import { ObjectTypeString } from "objects/ObjectDescriber";
import { pond } from "protobuf-ts/pond";
import { useGlobalState, useSnackbar } from "providers";
import { useDevicePresetAPI } from "providers/pond/devicePresetAPI";
import React, { useEffect, useState } from "react";
interface Props {
preset: DevicePreset;
existing?: boolean;
objectKey?: string;
objectType?: pond.ObjectType;
addCallback?: (preset: DevicePreset, tempKey: string) => void;
removeCallback?: (preset: DevicePreset) => void;
updateCallback?: (oldPreset: DevicePreset, newPreset: DevicePreset) => void;
}
export default function DevicePresetCard(props: Props) {
const {
preset,
existing,
objectKey,
objectType,
addCallback,
removeCallback,
updateCallback
} = props;
const devicePresetAPI = useDevicePresetAPI();
const [presetName, setPresetName] = useState("");
const [presetType, setPresetType] = useState<pond.PresetType>(1);
const [controllerType, setControllerType] = useState<pond.ControllerType>(1);
const [openDeleteConfirmation, setOpenDeleteConfirmation] = useState(false);
const { openSnack } = useSnackbar();
const [tempString, setTempString] = useState("0");
const [tempC, setTempC] = useState(0);
const [humString, setHumString] = useState("0");
const [hum, setHum] = useState(0);
const isMobile = useMobile();
const [{user}] = useGlobalState();
useEffect(() => {
setPresetName(preset.name);
setPresetType(preset.settings.type);
setControllerType(preset.settings.controller);
setTempC(preset.settings.temperature);
setHum(preset.settings.humidity);
setHumString(preset.settings.humidity.toString());
let displayTemp = preset.settings.temperature.toString();
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
displayTemp = ((preset.settings.temperature * 9) / 5 + 32).toFixed();
}
setTempString(displayTemp);
}, [preset, user]);
const saveNewPreset = () => {
let typestring;
if (objectType) {
typestring = ObjectTypeString(objectType);
}
let p = preset;
p.name = presetName;
p.settings.type = presetType;
p.settings.controller = controllerType;
p.settings.temperature = tempC;
p.settings.humidity = hum;
devicePresetAPI
.addDevicePreset(p.name, p.settings, objectKey, typestring)
.then(resp => {
//get the temp key from the preset
let temp = p.key;
//update the key in the preset with the key that came back from the add
p.key = resp.data.key;
//use the callback function passing in the preset and the temp key
if (addCallback) {
addCallback(p, temp);
}
openSnack("New preset saved");
})
.catch(err => {
openSnack("Failed to save new preset");
});
};
const updatePreset = () => {
let newPreset = cloneDeep(preset);
let oldPreset = cloneDeep(preset);
newPreset.name = presetName;
newPreset.settings.type = presetType;
newPreset.settings.controller = controllerType;
newPreset.settings.temperature = tempC;
newPreset.settings.humidity = hum;
devicePresetAPI
.updateDevicePreset(newPreset.key, newPreset.name, newPreset.settings)
.then(resp => {
if (updateCallback) {
updateCallback(oldPreset, newPreset);
}
openSnack("Preset Updated");
})
.catch(err => {
openSnack("There was a problem updating the preset");
});
};
const removePreset = () => {
devicePresetAPI
.removeDevicePreset(preset.key)
.then(resp => {
openSnack("Preset has been deleted");
setOpenDeleteConfirmation(false);
if (removeCallback) {
removeCallback(preset);
}
})
.catch(err => {
openSnack("There was a problem deleting this preset");
});
};
const deleteConfirmation = () => {
return (
<Dialog
open={openDeleteConfirmation}
onClose={() => {
setOpenDeleteConfirmation(false);
}}>
<DialogTitle>Delete Preset</DialogTitle>
<DialogContent>This action will delete the selected preset</DialogContent>
<DialogActions>
<Button
onClick={() => {
setOpenDeleteConfirmation(false);
}}>
Cancel
</Button>
<Button
variant="contained"
color="primary"
onClick={() => {
removePreset();
}}>
Confirm
</Button>
</DialogActions>
</Dialog>
);
};
const presetComparitors = () => {
if (controllerType === pond.ControllerType.CONTROLLER_TYPE_HEATER) {
switch (presetType) {
case pond.PresetType.PRESET_TYPE_DRYING:
return { temp: "below", hum: "above" };
case pond.PresetType.PRESET_TYPE_COOLING:
return { temp: "below", hum: "above" };
}
} else {
//the controller type is a fan
switch (presetType) {
case pond.PresetType.PRESET_TYPE_DRYING:
return { temp: "above", hum: "below" };
case pond.PresetType.PRESET_TYPE_COOLING:
return { temp: "below", hum: "below" };
case pond.PresetType.PRESET_TYPE_HYDRATING:
return { temp: "below", hum: "above" };
}
}
//replace having a default case with final return
return { temp: "", hum: "" };
};
return (
<Card raised style={{ padding: 10, width: "100%" }}>
{deleteConfirmation()}
<Grid container direction="row" alignContent="center" alignItems="center">
<Grid size={{xs:12, sm:6}}>
<TextField
variant="outlined"
margin="normal"
style={{ width: "100%" }}
value={presetName}
onChange={e => {
setPresetName(e.target.value);
}}
/>
</Grid>
<Grid size={{xs:12, sm:6}}>
{presetType !== pond.PresetType.PRESET_TYPE_UNKNOWN &&
controllerType !== pond.ControllerType.CONTROLLER_TYPE_UNKNOWN && (
<Box paddingLeft={2}>
<Typography>
Toggle{" "}
{controllerType === pond.ControllerType.CONTROLLER_TYPE_HEATER ? "heater" : "fan"}{" "}
on when temperature {presetComparitors().temp} and humidity{" "}
{presetComparitors().hum} set points
</Typography>
</Box>
)}
</Grid>
<Grid size={6}>
{/* type */}
<TextField
style={{ width: "100%", marginRight: 5 }}
margin="normal"
select
label="Type"
variant="outlined"
value={presetType}
onChange={e => {
setPresetType(+e.target.value as pond.PresetType);
}}>
<MenuItem
key={pond.PresetType.PRESET_TYPE_UNKNOWN}
value={pond.PresetType.PRESET_TYPE_UNKNOWN}>
Mode For Preset
</MenuItem>
<MenuItem
key={pond.PresetType.PRESET_TYPE_DRYING}
value={pond.PresetType.PRESET_TYPE_DRYING}>
Drying
</MenuItem>
<MenuItem
key={pond.PresetType.PRESET_TYPE_COOLING}
value={pond.PresetType.PRESET_TYPE_COOLING}>
Cooling
</MenuItem>
<MenuItem
key={pond.PresetType.PRESET_TYPE_HYDRATING}
value={pond.PresetType.PRESET_TYPE_HYDRATING}>
Hydrating
</MenuItem>
</TextField>
</Grid>
<Grid size={6}>
{/* controller type */}
<TextField
style={{ width: "100%", marginLeft: 5 }}
margin="normal"
select
label="Controller Type"
variant="outlined"
value={controllerType}
onChange={e => {
setControllerType(+e.target.value as pond.ControllerType);
}}>
<MenuItem
key={pond.ControllerType.CONTROLLER_TYPE_UNKNOWN}
value={pond.ControllerType.CONTROLLER_TYPE_UNKNOWN}>
Controller For Preset
</MenuItem>
<MenuItem
key={pond.ControllerType.CONTROLLER_TYPE_HEATER}
value={pond.ControllerType.CONTROLLER_TYPE_HEATER}>
Heater
</MenuItem>
<MenuItem
key={pond.ControllerType.CONTROLLER_TYPE_FAN}
value={pond.ControllerType.CONTROLLER_TYPE_FAN}>
Fan
</MenuItem>
</TextField>
</Grid>
{/* temp slider */}
{isMobile ? (
<Grid size={12}>
<Box
display="flex"
alignContent="center"
alignItems="center"
justifyContent="space-between">
<Typography>Temperature</Typography>
<TextField
style={{ marginLeft: 5 }}
margin="dense"
variant="outlined"
type="number"
InputProps={{
endAdornment: (
<InputAdornment position="end">
{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F"
: "°C"}
</InputAdornment>
)
}}
error={isNaN(+tempString)}
value={tempString}
onChange={e => {
if (!isNaN(+e.target.value)) {
let temp = +e.target.value;
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = Math.round((((temp - 32) * 5) / 9) * 10) / 10;
}
setTempC(temp);
}
setTempString(e.target.value);
}}
/>
</Box>
<Box paddingLeft={2} paddingRight={2}>
<Slider
//orientation="vertical"
value={tempC}
min={-40}
max={40}
valueLabelDisplay="auto"
valueLabelFormat={value => {
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
return ((value * 9) / 5 + 32).toFixed() + "°F";
}
return value.toFixed() + "°C";
}}
onChange={(_, value) => {
setTempC(value as number);
setTempString(value.toString());
}}
/>
</Box>
</Grid>
) : (
<React.Fragment>
<Grid size={2}>
<Typography>Temperature</Typography>
</Grid>
<Grid size={7}>
<Box paddingRight={2}>
<Slider
//orientation="vertical"
value={tempC}
min={-40}
max={40}
valueLabelDisplay="auto"
valueLabelFormat={value => {
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
return ((value * 9) / 5 + 32).toFixed() + "°F";
}
return value.toFixed() + "°C";
}}
onChange={(_, value) => {
setTempC(value as number);
setTempString(value.toString());
}}
/>
</Box>
</Grid>
<Grid size={3}>
<TextField
style={{ width: "100%" }}
margin="dense"
variant="outlined"
type="number"
InputProps={{
endAdornment: (
<InputAdornment position="end">
{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F"
: "°C"}
</InputAdornment>
)
}}
error={isNaN(+tempString)}
value={tempString}
onChange={e => {
if (!isNaN(+e.target.value)) {
let temp = +e.target.value;
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = Math.round((((temp - 32) * 5) / 9) * 10) / 10;
}
setTempC(temp);
}
setTempString(e.target.value);
}}
/>
</Grid>
</React.Fragment>
)}
{/* humidity slider */}
{isMobile ? (
<Grid size={12}>
<Box
display="flex"
alignContent="center"
alignItems="center"
justifyContent="space-between">
<Typography>Humidity</Typography>
<TextField
style={{ marginLeft: 5 }}
margin="dense"
variant="outlined"
type="number"
InputProps={{
endAdornment: <InputAdornment position="end">%</InputAdornment>
}}
error={isNaN(+humString)}
value={humString}
onChange={e => {
if (!isNaN(+humString)) {
setHum(+e.target.value);
}
setHumString(e.target.value);
}}
/>
</Box>
<Box paddingLeft={2} paddingRight={2}>
<Slider
//orientation="vertical"
value={hum}
min={0}
max={100}
valueLabelDisplay="auto"
valueLabelFormat={value => value.toFixed() + "%"}
onChange={(_, value) => {
setHum(value as number);
setHumString(value.toString());
}}
/>
</Box>
</Grid>
) : (
<React.Fragment>
<Grid size={2}>
<Typography>Humidity</Typography>
</Grid>
<Grid size={7}>
<Box paddingRight={2}>
<Slider
//orientation="vertical"
value={hum}
min={0}
max={100}
valueLabelDisplay="auto"
valueLabelFormat={value => value.toFixed() + "%"}
onChange={(_, value) => {
setHum(value as number);
setHumString(value.toString());
}}
/>
</Box>
</Grid>
<Grid size={3}>
<TextField
style={{ width: "100%" }}
margin="dense"
variant="outlined"
type="number"
InputProps={{
endAdornment: <InputAdornment position="end">%</InputAdornment>
}}
error={isNaN(+humString)}
value={humString}
onChange={e => {
if (!isNaN(+humString)) {
setHum(+e.target.value);
}
setHumString(e.target.value);
}}
/>
</Grid>
</React.Fragment>
)}
</Grid>
<Box display="flex" justifyContent="flex-end" alignContent="flex-end" marginTop={1}>
{existing && (
<Button
variant="contained"
style={{ background: "red" }}
onClick={() => {
setOpenDeleteConfirmation(true);
}}>
Delete
</Button>
)}
{existing ? (
<Button
style={{ marginLeft: 5 }}
variant="contained"
color="primary"
onClick={() => {
updatePreset();
}}>
Update
</Button>
) : (
<Button
disabled={
presetType === pond.PresetType.PRESET_TYPE_UNKNOWN ||
controllerType === pond.ControllerType.CONTROLLER_TYPE_UNKNOWN
}
onClick={() => saveNewPreset()}
variant="contained"
color="primary">
Save
</Button>
)}
</Box>
</Card>
);
}