added bins page (mostly stubbed)

This commit is contained in:
Carter 2025-03-10 10:29:20 -06:00
parent 044b2ad8f4
commit 3bab272744
6 changed files with 1694 additions and 5 deletions

238
src/models/Bin.ts Normal file
View file

@ -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<HTMLElement>, 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;
}
}

View file

@ -1,5 +1,5 @@
export * from "./Backpack";
// export * from "./Bin";
export * from "./Bin";
// export * from "./BinYard";
export * from "./Component";
export * from "./Device";

View file

@ -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) {
<Route path="teams/*" element={<TeamsRoute/>} />
<Route path="devices/*" element={<DevicesRoute/>} />
<Route path="groups/*" element={<GroupsRoute/>} />
<Route path="bins/*" element={<BinsRoute/>} />
</Routes>
)
}
@ -90,6 +92,37 @@ export default function Router(props: Props) {
);
};
const BinsRoute = () => {
return (
<div>
<Routes>
<Route
key="Bins page route"
path="" // "/settings/basic"
element={<Bins key={"Bins page"} />}
/>
{/* <Route
path="/:binID" // "/settings/basic"
element={<Bin />}
/>
<Route
path="/:deviceID/components/:componentID" // "/settings/basic"
element={<DeviceComponent />}
/>
<Route
path="/:deviceID/history" // "/settings/basic"
element={<DeviceHistory />}
/>
<Route
path="/:deviceID/*" // "/settings/basic"
element={<RelativeRoutes />}
/> */}
</Routes>
</div>
);
};
const GroupsRoute = () => {
return (
<div>
@ -154,9 +187,10 @@ export default function Router(props: Props) {
<Route path="/callback" element={<Navigate to="/" />} />
{/* <Route path="/team/:teamID" element={<Navigate to="/teams/:teamID" />} /> */}
{/* <Route path="/device/:deviceID" element={<Navigate to="/devices/:devicesID" />} /> */}
<Route path="" element={<Navigate to="/devices" />} />
{/* Page routes */}
<Route index element={<Typography>Hello!</Typography>} />
{/* <Route index element={<Typography>Hello!</Typography>} /> */}
<Route path="users" element={<Users/>} />
<Route path="/logout" element={<Logout />} />
{/*

View file

@ -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 (
<List className={classes.list} component="nav">
<Tooltip title="Bins" placement="right">
<ListItemButton
id="tour-dashboard"
component={Link}
to="/bins"
onClick={onClose}
classes={getClasses("/bin")}
>
<ListItemIcon>
<BinsIcon />
</ListItemIcon>
{open && <ListItemText primary="Devices" />}
</ListItemButton>
</Tooltip>
<Tooltip title="Devices" placement="right">
<ListItemButton
id="tour-dashboard"

1400
src/pages/Bins.tsx Normal file

File diff suppressed because it is too large Load diff

View file

@ -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<Map<string, string>>(new Map<string, string>())
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);