diff --git a/src/models/Bin.ts b/src/models/Bin.ts new file mode 100644 index 0000000..c500bab --- /dev/null +++ b/src/models/Bin.ts @@ -0,0 +1,238 @@ +import GrainDescriber from "grain/GrainDescriber"; +import { cloneDeep } from "lodash"; +// import { MarkerData } from "Maps/mapMarkers/Markers"; +import { pond } from "protobuf-ts/pond"; +import { stringToMaterialColour } from "utils/strings"; +import { or } from "utils/types"; + +export class Bin { + public settings: pond.BinSettings = pond.BinSettings.create(); + public status: pond.BinStatus = pond.BinStatus.create(); + public permissions: pond.Permission[] = []; + + public static create(pb?: pond.Bin): Bin { + let my = new Bin(); + if (pb) { + my.settings = pond.BinSettings.fromObject(cloneDeep(or(pb.settings, {}))); + my.status = pond.BinStatus.fromObject(cloneDeep(or(pb.status, {}))); + my.permissions = pb.binPermissions; + } + return my; + } + + public static clone(other?: Bin): Bin { + if (other) { + return Bin.create( + pond.Bin.fromObject({ + settings: cloneDeep(other.settings), + status: cloneDeep(other.status), + binPermissions: cloneDeep(other.permissions) + }) + ); + } + return Bin.create(); + } + + public static any(data: any): Bin { + return Bin.create(pond.Bin.fromObject(cloneDeep(data))); + } + + public setLocation(long: number, lat: number): void { + this.settings.location = pond.Location.create(); + this.settings.location.longitude = long; + this.settings.location.latitude = lat; + } + + public removeLocation(): void { + this.settings.location = null; + } + + public binShape(): pond.BinShape | undefined { + return this.settings.specs?.shape; + } + + public key(): string { + return this.settings.key; + } + + public name(): string { + return this.settings.name !== "" ? this.settings.name : "Bin " + this.key(); + } + + public grain(): pond.Grain { + let g: pond.Grain = pond.Grain.GRAIN_NONE; + if (this.settings.inventory) { + g = this.settings.inventory.grainType; + } + return g; + } + + public getLocation(): pond.Location | null | undefined { + return this.settings.location; + } + + public binMapped(): boolean { + let location = this.getLocation(); + return ( + location !== undefined && + location !== null && + location.latitude !== 0 && + location.longitude !== 0 + ); + } + + public storage(): pond.BinStorage { + return !this.settings.storage + ? pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN + : this.settings.storage; + } + + public customType(): string { + let c = ""; + if (this.settings.inventory) { + c = this.settings.inventory.customTypeName; + } + return c; + } + + public empty(): boolean { + return this.settings.inventory?.empty || this.settings.inventory?.grainBushels === 0; + } + + public objectType(): pond.ObjectType { + return pond.ObjectType.OBJECT_TYPE_BIN; + } + + public objectTypeString(): string { + return "Bin"; + } + + public subtype(): string { + return this.settings.inventory?.grainSubtype ?? ""; + } + + public grainColour(): string { + let colour = ""; + if (this.settings.inventory) { + if (this.grain() === pond.Grain.GRAIN_CUSTOM) { + if (this.customType() !== "") { + colour = stringToMaterialColour(this.customType()); + } + } else { + colour = GrainDescriber(this.grain()).colour; + } + } + return colour; + } + + public fillPercent(): number { + let fill = 0; + if (this.settings.inventory && this.settings.specs) { + fill = Math.round( + (this.settings.inventory.grainBushels / this.settings.specs.bushelCapacity) * 100 + ); + } + return fill; + } + + public grainName(): string { + if (this.grain() !== pond.Grain.GRAIN_INVALID) { + if (this.grain() === pond.Grain.GRAIN_CUSTOM) { + return this.customType(); + } else { + return GrainDescriber(this.grain()).name; + } + } else { + return ""; + } + } + + public binFillCap(): string { + let fillCap = ""; + if (this.settings.specs && this.settings.inventory) { + fillCap = this.settings.inventory.grainBushels + "/" + this.settings.specs.bushelCapacity; + } + return fillCap; + } + + public location(): pond.Location | undefined | null { + let loc = this.settings.location; + return loc; + } + + public fanID(): number { + return this.settings.fanId; + } + + public supportedGrain(): boolean { + let s = false; + if ( + this.grain() !== pond.Grain.GRAIN_NONE && + this.grain() !== pond.Grain.GRAIN_CUSTOM && + this.grain() !== pond.Grain.GRAIN_INVALID + ) { + s = true; + } + return s; + } + + // public getMarkerData( + // clickFunc?: (event: React.PointerEvent, index: number, isMobile: boolean) => void, + // updateFunc?: (location: pond.Location) => void + // ): MarkerData { + // let m: MarkerData = { + // centered: true, + // longitude: this.location()?.longitude ?? 0, + // latitude: this.location()?.latitude ?? 0, + // title: this.name(), + // colour: this.grainColour(), + // visibleLevels: { min: 17 }, + // graphPercent: this.fillPercent(), + // customSize: this.settings.theme?.height, + // details: [this.name() + ", " + this.binFillCap()], + // clickFunc: clickFunc, + // updateFunc: updateFunc + // }; + // return m; + // } + + /** + * returns the bushels per tonne set in the bins settings, if not set will return 1 + * @returns 1 or bushels per tonne + */ + public bushelsPerTonne(): number { + //trying to avoid a divide by 0 error by only returning a value greater than 0 + //since to get the weight you divide the current bushels by the bushels per tonne + let bpt = 1; + if (this.settings.inventory) { + if (this.settings.inventory.bushelsPerTonne > 0) { + bpt = this.settings.inventory.bushelsPerTonne; + } + } + 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(); + } + return Math.round(weight * 100) / 100; + } + + /** + * gets the enum value for the inventory control in the bins inventory + */ + public inventoryControl(): pond.BinInventoryControl { + let c = pond.BinInventoryControl.BIN_INVENTORY_CONTROL_UNKNOWN; + if (this.settings.inventory) { + return this.settings.inventory.inventoryControl; + } + return c; + } +} diff --git a/src/models/index.ts b/src/models/index.ts index 8a41dd3..b38754b 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,5 +1,5 @@ export * from "./Backpack"; -// export * from "./Bin"; +export * from "./Bin"; // export * from "./BinYard"; export * from "./Component"; export * from "./Device"; diff --git a/src/navigation/Router.tsx b/src/navigation/Router.tsx index a3907be..83b1d3c 100644 --- a/src/navigation/Router.tsx +++ b/src/navigation/Router.tsx @@ -14,6 +14,7 @@ import GroupsPage from "pages/Groups"; import GroupPage from "pages/Group"; import { ErrorBoundary } from "react-error-boundary"; import DeviceComponent from "pages/DeviceComponent"; +import Bins from "pages/Bins"; const DeviceHistory = lazy(() => import("pages/DeviceHistory")); @@ -37,6 +38,7 @@ export default function Router(props: Props) { } /> } /> } /> + } /> ) } @@ -90,6 +92,37 @@ export default function Router(props: Props) { ); }; + const BinsRoute = () => { + return ( +
+ + } + /> + {/* } + /> + } + /> + } + /> + } + /> */} + +
+ ); + }; + + const GroupsRoute = () => { return (
@@ -154,9 +187,10 @@ export default function Router(props: Props) { } /> {/* } /> */} {/* } /> */} + } /> {/* Page routes */} - Hello!} /> + {/* Hello!} /> */} } /> } /> {/* diff --git a/src/navigation/SideNavigator.tsx b/src/navigation/SideNavigator.tsx index 43d18bf..a81cb4d 100644 --- a/src/navigation/SideNavigator.tsx +++ b/src/navigation/SideNavigator.tsx @@ -6,6 +6,7 @@ import { useWidth } from "../hooks/useWidth"; import { makeStyles } from "@mui/styles"; import BindaptIcon from "../products/Bindapt/BindaptIcon"; import { Link, useLocation } from "react-router-dom"; +import BinsIcon from "products/Bindapt/BinsIcon"; const drawerWidth = 230; @@ -103,6 +104,20 @@ export default function SideNavigator(props: Props) { return ( + + + + + + {open && } + + { + return ({ + "@keyframes pulsate": { + to: { + boxShadow: "0 0 0 16px" + yellow["500"] + "00" + } + }, + green: { + color: green["500"], + "&:hover": { + color: green["600"] + } + }, + gridList: { + width: "100%", + flexWrap: "nowrap", + transform: "translateZ(0)" + }, + pulse: { + boxShadow: "0 0 0 0 " + yellow["500"] + "75", + animation: "$pulsate 1.75s infinite cubic-bezier(0.66, 0.33, 0, 1)" + }, + icon: { + padding: 6, + width: 36, + height: 36, + borderRadius: "18px", + background: "rgba(0,0,0,0)", + "&:hover": { + background: + "radial-gradient(closest-side, rgba(150, 150, 150, 0.5) 50%, rgba(150, 150, 150, 0.5))" + } + }, + accordion: { + background: darken(theme.palette.background.paper, 0.4) + } + }); +}); + +interface PaginatedBins { + bins: Bin[]; + binsOffset: number; + binsTotal: number; +} + +interface Props { + yardFilter?: string; + yards?: pond.BinYardSettings[]; + insert?: boolean; +} + +export default function Bins(props: Props) { + const classes = useStyles(); + const isMobile = useMobile(); + const binAPI = useBinAPI(); + // const binyardAPI = useBinYardAPI(); + const interactionsAPI = useInteractionsAPI(); + const [{ user, as }] = useGlobalState(); + const [allLoading, setAllLoading] = useState(false); + const [binsLoading, setBinsLoading] = useState(false); + const [addBinOpen, setAddBinOpen] = useState(false); + const [paginatedBins, setPaginatedBins] = useState({ + bins: [], + binsOffset: 0, + binsTotal: 0 + }); + const [binMetrics, setBinMetrics] = useState(); + const [binMenuAnchorEl, setBinMenuAnchorEl] = useState(null); + const [menuAnchorEl, setMenuAnchorEl] = useState(null); + const [contentFilter, setContentFilter] = useState(""); + const [yardFilter, setYardFilter] = useState(""); + const [order, setOrder] = useState<"asc" | "desc">("asc"); + const [orderBy, setOrderBy] = useState("name"); + const binsLimit = 40; + const theme = useTheme(); + const width = useWidth(); + const [yards, setYards] = useState(props.yards ?? []); + const [yardPermissions, setYardPermissions] = useState>({}); + const [loadingAs, setLoadingAs] = useState(as); + const [loadingFilter, setLoadingFilter] = useState("init"); + const [displayGrain, setDisplayGrain] = useState(true); + const [displayFert, setDisplayFert] = useState(false); + const [displayEmpty, setDisplayEmpty] = useState(false); + const [grainBins, setGrainBins] = useState([]); + const [fertilizerBins, setFertilizerBins] = useState([]); + const [emptyBins, setEmptyBins] = useState([]); + // const [grainBags, setGrainBags] = useState([]); + // const [displayedInventory, setDisplayedInventory] = useState([]); + // const [displayedFertilizer, setDisplayedFertilizer] = useState([]); + const [carouselIndex, setCarouselIndex] = useState(0); + const [addBagOpen, setAddBagOpen] = useState(false); + const [showing, setShowing] = useState<"all" | "bins" | "bags">("all"); + //const [binGridView, setBinGridView] = useState(false); + const [binView, setBinView] = useState<"grid" | "scroll" | "list">("grid"); + const [bagView, setBagView] = useState<"grid" | "scroll" | "list">("grid"); + const [expandTotal, setExpandTotal] = useState(false); + const [expandUtilization, setExpandUtilization] = useState(false); + const [expandFanTable, setExpandFanTable] = useState(false); + const [openQrGenerator, setOpenQrGenerator] = useState(false); + // const [qrKeyList, setQrKeyList] = useState([]); + const [totalFanControllers, setTotalFanControllers] = useState(0); + const [totalFanControllersOn, setTotalFanControllersOn] = useState(0); + const [yardsLoading, setYardsLoading] = useState(false); + const [openAddAlerts, setOpenAddAlerts] = useState(false); + const [alertBins, setAlertBins] = useState([]); + const { openSnack } = useSnackbar(); + // const history = useHistory(); + const navigate = useNavigate() + const [cardValDisplay, setCardValDisplay] = useState<"high" | "low" | "average">("average"); + const [scrollTranslations, setScrollTranslations] = useState({ + bins: 0, + empty: 0, + fertilizer: 0 + }); + + // const StyledToggleButtonGroup = withStyles(theme => ({ + // grouped: { + // //margin: theme.spacing(-0.5), + // border: "none", + // padding: theme.spacing(1), + // "&:not(:first-child):not(:last-child)": { + // borderRadius: 24, + // marginRight: theme.spacing(0.5), + // marginLeft: theme.spacing(0.5) + // }, + // "&:first-child": { + // borderRadius: 24, + // marginLeft: theme.spacing(0.25) + // }, + // "&:last-child": { + // borderRadius: 24, + // marginRight: theme.spacing(0.25) + // } + // }, + // root: { + // backgroundColor: darken( + // theme.palette.background.paper, + // getThemeType() === "light" ? 0.05 : 0.25 + // ), + // borderRadius: 24, + // content: "border-box" + // } + // }))(ToggleButtonGroup); + + // const StyledToggle = withStyles({ + // root: { + // backgroundColor: "transparent", + // overflow: "visible", + // content: "content-box", + // "&$selected": { + // backgroundColor: "gold", + // color: "black", + // borderRadius: 24, + // fontWeight: "bold" + // }, + // "&$selected:hover": { + // backgroundColor: "rgb(255, 255, 0)", + // color: "black", + // borderRadius: 24 + // } + // }, + // selected: {} + // })(ToggleButton); + + useEffect(() => { + let ebt = sessionStorage.getItem("expandBinTotal"); + if (ebt === "true") { + setExpandTotal(true); + } else { + setExpandTotal(false); + } + + let ebu = sessionStorage.getItem("expandBinUtilization"); + if (ebu === "true") { + setExpandUtilization(true); + } else { + setExpandUtilization(false); + } + + let binsView = sessionStorage.getItem("binsView"); + if (binsView === "scroll") { + setBinView("scroll"); + } else { + setBinView("grid"); + } + + let bagsView = sessionStorage.getItem("bagsView"); + if (bagsView === "scroll") { + setBagView("scroll"); + } else { + setBagView("grid"); + } + + let fanTable = sessionStorage.getItem("expandFanTable"); + if (fanTable === "true") { + setExpandFanTable(true); + } else { + setExpandFanTable(false); + } + }, []); + + useEffect(() => { + if (props.yardFilter) { + setYardFilter(props.yardFilter); + } + }, [props.yardFilter]); + + const loadBins = useCallback(() => { + //let filter = grainFilter; + let filter = yardFilter; + if (as === loadingAs && loadingFilter === filter) { + return; + } + setLoadingAs(as); + setLoadingFilter(filter); + setAllLoading(true); + setBinsLoading(true); + setDisplayEmpty(false); + setDisplayFert(false); + + binAPI + .listBinsAndData(binsLimit, 0, order, orderBy, filter, as, false) + .then(resp => { + let grain: Bin[] = []; + let empty: Bin[] = []; + let fert: Bin[] = []; + // let qr: QrCodeKey[] = []; + let b = resp.data.bins.map(b => Bin.any(b)); + b.forEach(bin => { + if (bin.empty()) { + empty.push(bin); + } else if (bin.storage() === pond.BinStorage.BIN_STORAGE_FERTILIZER) { + fert.push(bin); + } else { + grain.push(bin); + } + //build qr keys here + // qr.push({ + // key: bin.key(), + // name: bin.name() + // }); + }); + // setQrKeyList(qr); + setGrainBins(grain); + if (fert.length > 0) { + setDisplayFert(true); + } + if (empty.length > 0) { + setDisplayEmpty(true); + } + setFertilizerBins(fert); + setEmptyBins(empty); + setPaginatedBins({ + bins: b, + binsOffset: resp.data.nextOffset, + binsTotal: resp.data.total + }); + let metrics = pond.BinMetrics.fromObject(resp.data.metrics ?? {}); + // let inventory: GrainAmount[] = []; + // let fertInventory: GrainAmount[] = []; + if (metrics) { + metrics.grainInventory.forEach(grain => { + // inventory.push({ + // grain: grain.grainType, + // bushelAmount: grain.bushelAmount, + // grainName: grainName(grain.grainType) + // }); + }); + metrics.customInventory.forEach(invObject => { + if (invObject.storageType === pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN) { + // inventory.push({ + // grain: pond.Grain.GRAIN_CUSTOM, + // bushelAmount: invObject.amount, + // grainName: invObject.name + // }); + } else if (invObject.storageType === pond.BinStorage.BIN_STORAGE_FERTILIZER) { + // fertInventory.push({ + // grain: pond.Grain.GRAIN_CUSTOM, + // bushelAmount: invObject.amount * 35.239, + // grainName: invObject.name + // }); + } + }); + } + setBinMetrics(metrics); + // setDisplayedInventory(inventory); + // setDisplayedFertilizer(fertInventory); + if (yards.length === 0) { + let y: pond.BinYardSettings[] = []; + let p: Dictionary = {}; + // let sortedYards = resp.data.binYards.sort((a, b) => { + // let yard1 = BinYardModel.create(a); + // let yard2 = BinYardModel.create(b); + // return ( + // yard1.settings.userSort[as ?? user.id()] - yard2.settings.userSort[as ?? user.id()] + // ); + // }); + // sortedYards.forEach(yard => { + // let newYard = pond.BinYard.fromObject(yard ?? {}); + // if (newYard.settings) { + // y.push(newYard.settings); + // p[newYard.settings.key] = newYard.yardPermissions; + // } + // }); + // setYardPermissions(p); + // setYards(y); + } + + //move load of grain bags into this api call + // let bags = resp.data.bags.map(b => GrainBag.create(b)); + // setGrainBags(bags); + }) + .catch(err => { + setPaginatedBins({ bins: [], binsOffset: 0, binsTotal: 0 }); + setBinMetrics(pond.BinMetrics.create()); + setYards([]); + }) + .finally(() => { + setAllLoading(false); + setBinsLoading(false); + }); + }, [binAPI, yardFilter, order, orderBy, as, loadingAs, loadingFilter]); // eslint-disable-line react-hooks/exhaustive-deps + + //made this a seperate function because we only need to load the bins themselves, we do not need to load all the metric data again + const loadMoreBins = (filter: string, offset?: number, limit?: number) => { + setBinsLoading(true); + let binFilter = filter; + binFilter = binFilter + " " + yardFilter; + binAPI + .listBins(limit ?? binsLimit, offset ?? 0, order, orderBy, binFilter, as) + .then(resp => { + const newBins = resp.data.bins.map(b => Bin.any(b)); + setPaginatedBins({ + //the offset being passed in means to load more rather than a new set so combine the loaded bins with the existing ones rather than replace them + bins: offset ? paginatedBins.bins.concat(newBins) : newBins, + binsOffset: resp.data.nextOffset, + binsTotal: resp.data.total + }); + let empty: Bin[] = offset ? emptyBins : []; + let fert: Bin[] = offset ? fertilizerBins : []; + let grain: Bin[] = offset ? grainBins : []; + // let newKeyList: QrCodeKey[] = qrKeyList; + newBins.forEach(bin => { + if (bin.empty()) { + empty.push(bin); + } else if (bin.storage() === pond.BinStorage.BIN_STORAGE_FERTILIZER) { + fert.push(bin); + } else { + grain.push(bin); + } + // newKeyList.push({ + // key: bin.key(), + // name: bin.name() + // }); + }); + if (fert.length > 0) { + setDisplayFert(true); + } + if (empty.length > 0) { + setDisplayEmpty(true); + } + setGrainBins([...grain]); + setEmptyBins([...empty]); + setFertilizerBins([...fert]); + // setQrKeyList([...newKeyList]); + }) + .catch(err => { + setPaginatedBins({ bins: [], binsOffset: 0, binsTotal: 0 }); + }) + .finally(() => { + setBinsLoading(false); + }); + }; + + const searchYards = ( + search?: string, + limit?: number, + offset?: number, + order?: "asc" | "desc" + ) => { + setYardsLoading(true); + // binyardAPI.listBinYards(limit ?? binsLimit, offset ?? 0, order, "name", search).then(resp => { + // let y: pond.BinYardSettings[] = []; + // let p: Dictionary = {}; + // //sort the yards after loading + // let sortedYards = resp.data.yard.sort((a, b) => { + // let yard1 = BinYardModel.create(a); + // let yard2 = BinYardModel.create(b); + // return yard1.settings.userSort[as ?? user.id()] - yard2.settings.userSort[as ?? user.id()]; + // }); + // //loop through the sorted yards + // sortedYards.forEach(yard => { + // let newYard = pond.BinYard.fromObject(yard ?? {}); + // if (newYard.settings) { + // y.push(newYard.settings); + // p[newYard.settings.key] = newYard.yardPermissions; + // } + // }); + // if (y.length > 0) { + // //dont set the yards if it came back with nothing + // setYardPermissions(p); + // setYards(y); + // } + // setYardsLoading(false); + // }); + }; + + useEffect(() => { + loadBins(); + //if it is a drawer only show the bins, bags are only shown on the all tab so should not show up on the yard drawers + if (props.insert) { + setShowing("bins"); + } + }, [loadBins, props.insert, contentFilter]); + + const duplicateBin = (bin: Bin) => { + binAPI.addBin(bin.settings).then(resp => { + loadBins(); + }); + }; + + const addAlertsToCables = (bins: Bin[]) => { + let alertList: pond.AlertData[] = []; + bins.forEach(bin => { + //for each of the cable in the bin create alert data + bin.status.grainCables.forEach(cable => { + //split the location into its three parts + let locationParts = cable.address.split("-"); + if (locationParts[0] && locationParts[1] && locationParts[2]) { + //build the alert based on the bin + alertList.push( + pond.AlertData.create({ + device: cable.device, + nodeOne: cable.topNode, + interactionSubtype: Interaction.upToSubtype(cable.topNode), + condition: pond.InteractionCondition.create({ + measurementType: quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, + comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN, + value: describeMeasurement( + quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE + ).toStored(bin.settings.highTemp) + }), + componentLocation: quack.ComponentID.create({ + type: parseInt(locationParts[0]), + addressType: parseInt(locationParts[1]), + address: parseInt(locationParts[2]) + }) + }) + ); + } + }); + }); + interactionsAPI + .setAlertInteractions(alertList) + .then(resp => { + openSnack("Successfully added " + resp.data.numAlertsSet + " alerts"); + }) + .catch(err => { + console.log("errors found"); + }); + }; + + const addALertsDialog = () => { + return ( + setOpenAddAlerts(false)}> + Add Alert to Bin Cables + + This action will add an interaction to notify if the temp of any cable node in grain rises + above the high temp warning for all selected bins + + + + + + + ); + }; + + const binsContent = () => { + const { binsTotal } = paginatedBins; + + if (!allLoading && binsTotal <= 0) { + return ( + + No bins found + + Create a bin and it will appear here + + + ); + } + + if (binView === "list") { + return ( + // { + // let bin = data as Bin; + // let path = "/bins/" + bin.key(); + // navigate(path, { state: { bin: bin }}); + // }} + // customButtons={[ + // { + // label: "Set High Temp Notifications", + // function: objects => { + // setAlertBins(objects as Bin[]); + // setOpenAddAlerts(true); + // } + // } + // ]} + // /> + null + ); + } + + return ( + + + {displayGrain && ( + + + + + + {/* { + if (paginatedBins.bins.length < paginatedBins.binsTotal) { + loadMoreBins(contentFilter, paginatedBins.binsOffset); + setScrollTranslations({ ...scrollTranslations, bins: newTranslation }); + } + }} + />*/} + + + )} + {displayFert && ( + + + + + + {/* { + if (paginatedBins.bins.length < paginatedBins.binsTotal) { + loadMoreBins(contentFilter, paginatedBins.binsOffset); + setScrollTranslations({ ...scrollTranslations, fertilizer: newTranslation }); + } + }} + />*/} + + + )} + {displayEmpty && ( + + + + + + {/* { + if (paginatedBins.bins.length < paginatedBins.binsTotal) { + loadMoreBins(contentFilter, paginatedBins.binsOffset); + setScrollTranslations({ ...scrollTranslations, empty: newTranslation }); + } + }} + />*/} + + + )} + + + ); + }; + + const grainBagsContent = () => { + // if (!allLoading && grainBags.length <= 0) { + // return ( + // + // No bags found + // + // Create a grain bag and it will appear here + // + // + // ); + // } + + // if (bagView === "list") { + // return ( + // { + // let bag = data as GrainBag; + // let path = "/grainbags/" + bag.key(); + // history.push(path); + // }} + // /> + // ); + // } + + return ( + + + + + + + + {/* */} + + + + + ); + }; + + const binMenu = () => { + return ( + { + setMenuAnchorEl(null); + setAddBinOpen(false); + }} + disableAutoFocusItem> + {/* setAddBinOpen(true)} aria-label="Create Bin" button dense> + + + + + */} + + ); + }; + + const mobileViewCarousel = () => { + let length = 2; + let charts: JSX.Element[] = [ + // { + // let filter = ""; + // if (grain !== pond.Grain.GRAIN_NONE && grain !== pond.Grain.GRAIN_CUSTOM) { + // filter = pond.Grain[grain]; + // } else { + // filter = grainName; + // } + // setContentFilter(filter); + // loadMoreBins(filter); + // }} + // //activeGrain={grainFilter} + // />, + // { + // let filter = ""; + // if (grain !== pond.Grain.GRAIN_NONE && grain !== pond.Grain.GRAIN_CUSTOM) { + // filter = pond.Grain[grain]; + // } else { + // filter = grainName; + // } + // setContentFilter(filter); + // loadMoreBins(filter); + // }} + // customUnit={"L"} + // //activeGrain={grainFilter} + // /> + ]; + return ( + + + + + + + + {charts[carouselIndex]} + + + + + + + + ); + }; + + const desktopInventory = () => { + return ( + + + + {/* { + let filter = ""; + if (grain !== pond.Grain.GRAIN_NONE && grain !== pond.Grain.GRAIN_CUSTOM) { + filter = pond.Grain[grain]; + } else { + filter = grainName; + } + setContentFilter(filter); + loadMoreBins(filter); + }} + //activeGrain={grainFilter} + /> */} + + + {/* { + let filter = ""; + if (grain !== pond.Grain.GRAIN_NONE && grain !== pond.Grain.GRAIN_CUSTOM) { + filter = pond.Grain[grain]; + } else { + filter = grainName; + } + setContentFilter(filter); + loadMoreBins(filter); + }} + customUnit={"L"} + //activeGrain={grainFilter} + /> */} + + + + ); + }; + + const totalInventory = () => { + return ( + { + setExpandTotal(expanded); + sessionStorage.setItem("expandBinTotal", expanded.toString()); + }}> + }> + + Total Inventory + + + {isMobile ? mobileViewCarousel() : desktopInventory()} + + ); + }; + + const binUtilizationList = () => { + const hasInventory = binMetrics && binMetrics.grainInventory.length > 0; + const useWeight = getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT; + return ( + { + setExpandUtilization(expanded); + sessionStorage.setItem("expandBinUtilization", expanded.toString()); + }}> + }> + + Bin Utilization + + + + {hasInventory ? ( + + + Click a chart to filter bins by grain + + {/* + {binMetrics && + binMetrics.grainInventory.map((inv, key) => ( + + { + setContentFilter(pond.Grain[inv.grainType]); + loadMoreBins(pond.Grain[inv.grainType]); + }} + grainActive={contentFilter === pond.Grain[inv.grainType]} + /> + + ))} + {binMetrics && + binMetrics.customInventory.map((inv, key) => { + //default to bushel values + let amount = inv.amount; + let cap = inv.capacity; + let unit = " bu"; + if (inv.storageType === pond.BinStorage.BIN_STORAGE_FERTILIZER) { + 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"; + } + + return ( + + { + setContentFilter(inv.name); + loadMoreBins(inv.name); + }} + grainActive={contentFilter === inv.name} + customLabel={inv.name} + customColour={stringToMaterialColour(inv.name)} + /> + + ); + })} + */} + + ) : ( + + No active bins + + )} + + + ); + }; + + const binFanTable = () => { + return ( + { + setExpandFanTable(expanded); + sessionStorage.setItem("expandFanTable", expanded.toString()); + }}> + }> + + Fan Status{" "} + {totalFanControllers > 0 && !isMobile + ? " - " + totalFanControllersOn + " of " + totalFanControllers + " controllers on" + : ""} + + + + {/* { + setTotalFanControllers(total); + setTotalFanControllersOn(on); + }} + /> */} + + + ); + }; + + const binsHeader = () => { + return ( + { + setBinMenuAnchorEl(null); + }} + disableAutoFocusItem> + + + Order + + + + + + Order By + + + + + + { + setDisplayGrain(checked); + }} + /> + } + label={Grain} + /> + + + { + setDisplayFert(checked); + }} + /> + } + label={Fertilizer} + /> + + + { + setDisplayEmpty(checked); + }} + /> + } + label={Empty Bins} + /> + + { + setOpenQrGenerator(true); + }}> + Generate QR Codes + + + ); + }; + + const binsByGrainType = () => { + return ( + + + + + + Bins - ({paginatedBins.bins.length} of {paginatedBins.binsTotal}) + + {contentFilter && ( + + { + setContentFilter(""); + loadMoreBins(""); + }} + label={contentFilter} + /> + + )} + {(binView === "grid" || binView === "list") && + paginatedBins.bins.length < paginatedBins.binsTotal && ( + + )} + {/* + { + setCardValDisplay("low"); + }}> + Low + + { + setCardValDisplay("average"); + }}> + Average + + { + setCardValDisplay("high"); + }}> + High + + */} + + + + + {/* */} + {/* { + setBinView("grid"); + sessionStorage.setItem("binsView", "grid"); + }}> + + */} + {/* hidden at dustins request so that grid view and list are the only two */} + {/* { + setBinView("scroll"); + sessionStorage.setItem("binsView", "scroll"); + }}> + + */} + {/* { + setBinView("list"); + sessionStorage.setItem("binsView", "list"); + }}> + + + */} + { + let target = event.currentTarget; + setBinMenuAnchorEl(target); + }} + /> + + + + {binsLoading ? : binsContent()} + {binsHeader()} + + ); + }; + + const grainBagDisplay = () => { + return ( + + + + + + + + + Bags + + + + + + {/* + { + setBagView("grid"); + sessionStorage.setItem("bagsView", "grid"); + }}> + + */} + {/* hidden at dustins request so that grid view and list are the only two */} + {/* { + setBagView("scroll"); + sessionStorage.setItem("bagsView", "scroll"); + }}> + + */} + {/* { + setBagView("list"); + sessionStorage.setItem("bagsView", "list"); + }}> + + + */} + { + setAddBagOpen(true); + }}> + + + + + + + {allLoading ? : grainBagsContent()} + + ); + }; + + const { binsTotal } = paginatedBins; + + const page = () => { + return ( + + {addALertsDialog()} + {/* { + searchYards(search, limit, offset); + }} + grainBags={grainBags} + setShowing={(val: "all" | "bins" | "bags") => { + setShowing(val); + }} + /> */} + + + + {(showing === "all" || showing === "bins") && ( + + {binsByGrainType()} + + )} + + {allLoading ? : totalInventory()} + + + + + + {allLoading ? : binUtilizationList()} + + + + + + {allLoading ? : binFanTable()} + + + + + + {(showing === "all" || showing === "bags") && ( + + {grainBagDisplay()} + + )} + + + + {/* { + setAddBinOpen(false); + if (refresh) { + loadBins(); + } + }} + /> + { + setAddBagOpen(false); + }} + /> + { + setOpenQrGenerator(false); + }} + keyList={qrKeyList} + requiredUrlAffix={"bins"} + /> */} + {binMenu()} + {/* setAddBinOpen(true)} pulse={binsTotal < 1 && !allLoading} /> */} + + ); + }; + + return !props.insert ? {page()} : page(); +} diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx index 0543cbd..f8a5ea1 100644 --- a/src/pages/Devices.tsx +++ b/src/pages/Devices.tsx @@ -5,7 +5,7 @@ import { makeStyles } from "@mui/styles"; import ResponsiveTable, { Column } from "common/ResponsiveTable"; import ProvisionDevice from "device/ProvisionDevice"; import { pond } from "protobuf-ts/pond"; -import { useDeviceAPI, useGroupAPI } from "providers"; +import { useDeviceAPI, useGlobalState, useGroupAPI } from "providers"; import { useEffect, useState } from "react"; import PageContainer from "./PageContainer"; import { useMobile } from "hooks"; @@ -112,6 +112,8 @@ export default function Devices() { const [fieldContains, setFieldContains] = useState>(new Map()) + const [{ as }] = useGlobalState() + const updateGroups = (newGroups: Group[]) => { setGroups(newGroups); }; @@ -226,11 +228,11 @@ export default function Devices() { useEffect(() => { loadDevices() - }, [limit, page, order, orderBy, fieldContains, search, tab]) + }, [limit, page, order, orderBy, fieldContains, search, tab, as]) useEffect(() => { loadGroups() - }, [groupLimit, groupPage, orderGroup, orderGroupBy, searchGroup]) + }, [groupLimit, groupPage, orderGroup, orderGroupBy, searchGroup, as]) const handleChange = (event: any) => { setLimit(event.target.value);