added the sites page and all other required imports for it to work, also fixed the map card
This commit is contained in:
parent
c5afdc2c3d
commit
3990dfbf4f
14 changed files with 1595 additions and 21 deletions
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -10770,7 +10770,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/protobuf-ts": {
|
"node_modules/protobuf-ts": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#229e52a3f667df826cbd2e9b9de73fd0de70ab0c",
|
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#d1cf77df1de7c39e0cdf6663c7a25a364935f242",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"protobufjs": "^6.8.8"
|
"protobufjs": "^6.8.8"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ import { Beenhere } from "@mui/icons-material";
|
||||||
import { useThemeType } from "hooks";
|
import { useThemeType } from "hooks";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import ReactMapGL, { Marker } from "react-map-gl/mapbox-legacy";
|
import Map, { Marker } from "react-map-gl/mapbox-legacy";
|
||||||
|
|
||||||
|
const MAPBOX_TOKEN = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;
|
||||||
|
|
||||||
export interface Coordinate {
|
export interface Coordinate {
|
||||||
latitude: number;
|
latitude: number;
|
||||||
|
|
@ -79,27 +81,27 @@ export default function MapGL(props: Props) {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
console.log(elems)
|
||||||
return elems;
|
return elems;
|
||||||
};
|
};
|
||||||
|
|
||||||
let style =
|
let style =
|
||||||
themeType === "dark" ? "mapbox://styles/mapbox/dark-v10" : "mapbox://styles/mapbox/light-v10";
|
themeType === "dark" ? "mapbox://styles/mapbox/dark-v10" : "mapbox://styles/mapbox/light-v10";
|
||||||
return (
|
return (
|
||||||
<ReactMapGL
|
<Map
|
||||||
mapStyle={style}
|
mapStyle={style}
|
||||||
initialViewState={viewport}
|
initialViewState={viewport}
|
||||||
//width="100%"
|
mapboxAccessToken={MAPBOX_TOKEN}
|
||||||
//height="100%"
|
|
||||||
{...viewport}
|
{...viewport}
|
||||||
// onViewStateChange={(e: any) => {
|
onMove={(e: any) => {
|
||||||
// setViewport({
|
setViewport({
|
||||||
// longitude: e.viewState.longitude,
|
longitude: e.viewState.longitude,
|
||||||
// latitude: e.viewState.latitude,
|
latitude: e.viewState.latitude,
|
||||||
// zoom: e.viewState.zoom,
|
zoom: e.viewState.zoom,
|
||||||
// });
|
});
|
||||||
// }}
|
}}
|
||||||
>
|
>
|
||||||
{markers()}
|
{markers()}
|
||||||
</ReactMapGL>
|
</Map>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ const {terminals, currentTerminal } = props;
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [gateDialog, setGateDialog] = useState(false);
|
const [gateDialog, setGateDialog] = useState(false);
|
||||||
const [tablePage, setTablePage] = useState(0)
|
const [tablePage, setTablePage] = useState(0)
|
||||||
const [pageSize, setPageSize] = useState(5)
|
const [pageSize, setPageSize] = useState(10)
|
||||||
const [total, setTotal] = useState(0)
|
const [total, setTotal] = useState(0)
|
||||||
const [terminalMap, setTerminalMap] = useState<Map<string, string>>(new Map<string, string>());
|
const [terminalMap, setTerminalMap] = useState<Map<string, string>>(new Map<string, string>());
|
||||||
const gateAPI = useGateAPI();
|
const gateAPI = useGateAPI();
|
||||||
|
|
@ -77,11 +77,11 @@ const {terminals, currentTerminal } = props;
|
||||||
setGates(resp.data.gates.map(gate => Gate.create(gate)))
|
setGates(resp.data.gates.map(gate => Gate.create(gate)))
|
||||||
setTotal(resp.data.total)
|
setTotal(resp.data.total)
|
||||||
})
|
})
|
||||||
},[currentTerminal, search])
|
},[currentTerminal, search, pageSize, tablePage])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadGates()
|
loadGates()
|
||||||
},[currentTerminal, search])
|
},[loadGates])
|
||||||
|
|
||||||
const handleChange = (event: any) => {
|
const handleChange = (event: any) => {
|
||||||
setPageSize(event.target.value);
|
setPageSize(event.target.value);
|
||||||
|
|
@ -213,7 +213,7 @@ const {terminals, currentTerminal } = props;
|
||||||
renderGutter={isMobile || props.useMobile ? gutter : undefined}
|
renderGutter={isMobile || props.useMobile ? gutter : undefined}
|
||||||
gutterPadding={0}
|
gutterPadding={0}
|
||||||
rows={gates}
|
rows={gates}
|
||||||
setPage={(page) => {console.log(page)}}
|
setPage={(page) => {setTablePage(page)}}
|
||||||
total={total}
|
total={total}
|
||||||
onRowClick={(gate) => {goToGate(gate)}}
|
onRowClick={(gate) => {goToGate(gate)}}
|
||||||
hideKeys={isMobile || props.useMobile}
|
hideKeys={isMobile || props.useMobile}
|
||||||
|
|
|
||||||
319
src/jobsite/SiteActions.tsx
Normal file
319
src/jobsite/SiteActions.tsx
Normal file
|
|
@ -0,0 +1,319 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
IconButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
TextField,
|
||||||
|
Theme,
|
||||||
|
Tooltip
|
||||||
|
} from "@mui/material";
|
||||||
|
import { blue } from "@mui/material/colors";
|
||||||
|
import RemoveSelfIcon from "@mui/icons-material/ExitToApp";
|
||||||
|
import MoreIcon from "@mui/icons-material/MoreVert";
|
||||||
|
import ShareObjectIcon from "@mui/icons-material/Share";
|
||||||
|
import ObjectUsersIcon from "@mui/icons-material/AccountCircle";
|
||||||
|
import ObjectTeamsIcon from "@mui/icons-material/SupervisedUserCircle";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import ObjectUsers from "user/ObjectUsers";
|
||||||
|
import RemoveSelfFromObject from "user/RemoveSelfFromObject";
|
||||||
|
import ShareObject from "user/ShareObject";
|
||||||
|
import { isOffline } from "utils/environment";
|
||||||
|
import ObjectTeams from "teams/ObjectTeams";
|
||||||
|
import { siteScope, Site } from "models";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
import { useSiteAPI } from "providers";
|
||||||
|
import { useSnackbar } from "hooks";
|
||||||
|
import GroupSettingsIcon from "@mui/icons-material/Settings";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
shareIcon: {
|
||||||
|
color: blue["500"],
|
||||||
|
"&:hover": {
|
||||||
|
color: blue["600"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeIcon: {
|
||||||
|
color: "var(--status-alert)"
|
||||||
|
},
|
||||||
|
red: {
|
||||||
|
color: "var(--status-alert)"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
site: Site;
|
||||||
|
permissions: pond.Permission[];
|
||||||
|
refreshCallback: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OpenState {
|
||||||
|
share: boolean;
|
||||||
|
users: boolean;
|
||||||
|
teams: boolean;
|
||||||
|
settings: boolean;
|
||||||
|
removeSelf: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SiteActions(props: Props) {
|
||||||
|
const classes = useStyles();
|
||||||
|
const { site, permissions, refreshCallback } = props;
|
||||||
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
|
const [siteDialog, setSiteDialog] = useState(false);
|
||||||
|
const [deleteConfirmation, setDeleteConfirmation] = useState(false);
|
||||||
|
const siteAPI = useSiteAPI();
|
||||||
|
const [siteName, setSiteName] = useState(site.siteName());
|
||||||
|
const [update, setUpdate] = useState(false);
|
||||||
|
const { openSnack } = useSnackbar();
|
||||||
|
const [openState, setOpenState] = useState<OpenState>({
|
||||||
|
share: false,
|
||||||
|
users: false,
|
||||||
|
teams: false,
|
||||||
|
settings: false,
|
||||||
|
removeSelf: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupMenu = () => {
|
||||||
|
const canShare = permissions.includes(pond.Permission.PERMISSION_SHARE);
|
||||||
|
const canManageUsers = permissions.includes(pond.Permission.PERMISSION_USERS);
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
id="groupMenu"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={() => setAnchorEl(null)}
|
||||||
|
keepMounted
|
||||||
|
disableAutoFocusItem>
|
||||||
|
{!isOffline() && canShare && (
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, share: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ShareObjectIcon className={classes.shareIcon} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Share" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{!isOffline() && canManageUsers && (
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, users: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ObjectUsersIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Users" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{!isOffline() && canManageUsers && (
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, teams: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ObjectTeamsIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Teams" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, removeSelf: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<RemoveSelfIcon className={classes.red} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Leave" />
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialogs = () => {
|
||||||
|
const key = site.key();
|
||||||
|
const label = site.siteName();
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<ShareObject
|
||||||
|
scope={siteScope(key)}
|
||||||
|
label={label}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={openState.share}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, share: false })}
|
||||||
|
/>
|
||||||
|
<ObjectUsers
|
||||||
|
scope={siteScope(key)}
|
||||||
|
label={label}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={openState.users}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, users: false })}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
/>
|
||||||
|
<RemoveSelfFromObject
|
||||||
|
scope={siteScope(key)}
|
||||||
|
label={label}
|
||||||
|
isDialogOpen={openState.removeSelf}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, removeSelf: false })}
|
||||||
|
/>
|
||||||
|
{dialogForSite()}
|
||||||
|
{deleteDialog()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeSiteDialog = () => {
|
||||||
|
setSiteDialog(false);
|
||||||
|
setUpdate(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSite = () => {
|
||||||
|
let newSite = site;
|
||||||
|
newSite.settings.siteName = siteName;
|
||||||
|
|
||||||
|
siteAPI
|
||||||
|
.updateSite(newSite.key(), newSite.settings)
|
||||||
|
.then(resp => {
|
||||||
|
openSnack("Site Updated");
|
||||||
|
closeSiteDialog();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
openSnack("Could Not Update Site");
|
||||||
|
closeSiteDialog();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//deletes a site from the DB and removes the coordinates of that site from the array
|
||||||
|
const deleteSite = () => {
|
||||||
|
siteAPI
|
||||||
|
.removeSite(site.key())
|
||||||
|
.then(resp => {
|
||||||
|
openSnack("Site has been removed");
|
||||||
|
closeSiteDialog();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
openSnack("Site could not be removed");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const openUpdate = () => {
|
||||||
|
setUpdate(true);
|
||||||
|
setSiteDialog(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteDialog = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog
|
||||||
|
fullWidth
|
||||||
|
open={deleteConfirmation}
|
||||||
|
onClose={() => {
|
||||||
|
setDeleteConfirmation(false);
|
||||||
|
}}>
|
||||||
|
<DialogTitle>Delete Site</DialogTitle>
|
||||||
|
<DialogContent>Are you sure you wish to delete {site.siteName()}</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setDeleteConfirmation(false);
|
||||||
|
}}
|
||||||
|
color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={deleteSite} style={{ color: "red" }}>
|
||||||
|
Confirm
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//dialog the renders the forms for creating a new site
|
||||||
|
const dialogForSite = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog fullWidth fullScreen={false} open={siteDialog} onClose={closeSiteDialog}>
|
||||||
|
<DialogTitle>Enter Name of Site</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<TextField
|
||||||
|
value={siteName}
|
||||||
|
color="primary"
|
||||||
|
autoFocus
|
||||||
|
margin="dense"
|
||||||
|
id="name"
|
||||||
|
label="Site Name"
|
||||||
|
type="text"
|
||||||
|
fullWidth
|
||||||
|
onChange={e => setSiteName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
{update ? (
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setDeleteConfirmation(true);
|
||||||
|
}}
|
||||||
|
style={{ color: "red" }}>
|
||||||
|
Delete Site
|
||||||
|
</Button>
|
||||||
|
<Button onClick={updateSite} color="primary">
|
||||||
|
Update Site
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
) : (
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={closeSiteDialog} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
{/* <Button onClick={saveSite} color="primary" disabled={siteName === ""}>
|
||||||
|
Add Site
|
||||||
|
</Button> */}
|
||||||
|
</DialogActions>
|
||||||
|
)}
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{permissions.includes(pond.Permission.PERMISSION_WRITE) && (
|
||||||
|
<Tooltip title="Jobsite Settings">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
openUpdate();
|
||||||
|
}}>
|
||||||
|
<GroupSettingsIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<IconButton
|
||||||
|
aria-owns={anchorEl ? "groupMenu" : undefined}
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={(event: React.MouseEvent<HTMLButtonElement>) => setAnchorEl(event.currentTarget)}>
|
||||||
|
<MoreIcon />
|
||||||
|
</IconButton>
|
||||||
|
{groupMenu()}
|
||||||
|
{dialogs()}
|
||||||
|
<ObjectTeams
|
||||||
|
scope={siteScope(site.key())}
|
||||||
|
label="Teams"
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={openState.teams}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, teams: false })}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
33
src/models/ObjectHeater.ts
Normal file
33
src/models/ObjectHeater.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
|
||||||
|
export class ObjectHeater {
|
||||||
|
public settings: pond.ObjectHeaterSettings = pond.ObjectHeaterSettings.create();
|
||||||
|
public name: string = "Heater";
|
||||||
|
public key: string = "";
|
||||||
|
|
||||||
|
public static create(pb?: pond.ObjectHeater): ObjectHeater {
|
||||||
|
let my = new ObjectHeater();
|
||||||
|
if (pb) {
|
||||||
|
my.settings = pond.ObjectHeaterSettings.fromObject(cloneDeep(pb.settings ?? {}));
|
||||||
|
my.name = pb.name;
|
||||||
|
my.key = pb.key;
|
||||||
|
}
|
||||||
|
return my;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static clone(other?: ObjectHeater): ObjectHeater {
|
||||||
|
if (other) {
|
||||||
|
return ObjectHeater.create(
|
||||||
|
pond.ObjectHeater.fromObject({
|
||||||
|
settings: cloneDeep(other.settings)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ObjectHeater.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static any(data: any): ObjectHeater {
|
||||||
|
return ObjectHeater.create(pond.ObjectHeater.fromObject(cloneDeep(data)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,9 @@ import Terminals from "pages/Terminals";
|
||||||
import Gate from "pages/Gate";
|
import Gate from "pages/Gate";
|
||||||
import AviationMap from "pages/AviationMap";
|
import AviationMap from "pages/AviationMap";
|
||||||
import ConstructionSiteMap from "pages/ConstructionSiteMap";
|
import ConstructionSiteMap from "pages/ConstructionSiteMap";
|
||||||
|
import Sites from "pages/Sites";
|
||||||
|
import Site from "pages/Site";
|
||||||
|
//import Site from "pages/Site";
|
||||||
|
|
||||||
const DeviceHistory = lazy(() => import("pages/DeviceHistory"));
|
const DeviceHistory = lazy(() => import("pages/DeviceHistory"));
|
||||||
const DevicePage = lazy(() => import("pages/Device"));
|
const DevicePage = lazy(() => import("pages/Device"));
|
||||||
|
|
@ -51,6 +54,7 @@ export default function Router(props: Props) {
|
||||||
<Route path="bins/*" element={<BinsRoute/>} />
|
<Route path="bins/*" element={<BinsRoute/>} />
|
||||||
<Route path="mines/*" element={<MinesRoute/>} />
|
<Route path="mines/*" element={<MinesRoute/>} />
|
||||||
<Route path="terminals/*" element={<TerminalGatesRoute />} />
|
<Route path="terminals/*" element={<TerminalGatesRoute />} />
|
||||||
|
<Route path="jobsites/*" element={<JobsitesRoute />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -178,6 +182,28 @@ export default function Router(props: Props) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const JobsitesRoute = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Routes>
|
||||||
|
<Route
|
||||||
|
key="Jobsite page route"
|
||||||
|
path="" // "/settings/basic"
|
||||||
|
element={<Sites key={"Jobsites page"} />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/:siteKey" // "/settings/basic"
|
||||||
|
element={<Site />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/:siteKey/*" // "/settings/basic"
|
||||||
|
element={<RelativeRoutes />}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const GroupsRoute = () => {
|
const GroupsRoute = () => {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import { useAuth0 } from "@auth0/auth0-react";
|
||||||
import FieldsIcon from "products/AgIcons/FieldsIcon";
|
import FieldsIcon from "products/AgIcons/FieldsIcon";
|
||||||
import PlaneIcon from "products/AviationIcons/PlaneIcon";
|
import PlaneIcon from "products/AviationIcons/PlaneIcon";
|
||||||
import AirportMapIcon from "products/AviationIcons/AirportMapIcon";
|
import AirportMapIcon from "products/AviationIcons/AirportMapIcon";
|
||||||
|
import JobsiteIcon from "products/Construction/JobSiteIcon";
|
||||||
|
|
||||||
const drawerWidth = 230;
|
const drawerWidth = 230;
|
||||||
|
|
||||||
|
|
@ -265,6 +266,20 @@ export default function SideNavigator(props: Props) {
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
{(isAdCon || user.hasFeature("admin")) && (
|
||||||
|
<Tooltip title="Jobsites" placement="right">
|
||||||
|
<ListItemButton
|
||||||
|
id="tour-jobsites"
|
||||||
|
onClick={() => goTo("/jobsites")}
|
||||||
|
classes={getClasses("/jobsites")}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<JobsiteIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
{open && <ListItemText primary="Jobsites" />}
|
||||||
|
</ListItemButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</List>
|
</List>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
409
src/objectHeater/ObjectHeaterCard.tsx
Normal file
409
src/objectHeater/ObjectHeaterCard.tsx
Normal file
|
|
@ -0,0 +1,409 @@
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Grid2 as Grid,
|
||||||
|
ListItemAvatar,
|
||||||
|
ListItemText,
|
||||||
|
Theme,
|
||||||
|
Typography,
|
||||||
|
useTheme
|
||||||
|
} from "@mui/material";
|
||||||
|
import { GetDefaultDateRange } from "common/time/DateRange";
|
||||||
|
import UnitMeasurementSummary from "component/UnitMeasurementSummary";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import { Component, Device } from "models";
|
||||||
|
import { ObjectHeater } from "models/ObjectHeater";
|
||||||
|
import { UnitMeasurement } from "models/UnitMeasurement";
|
||||||
|
import moment from "moment";
|
||||||
|
import { GetComponentIcon } from "pbHelpers/ComponentType";
|
||||||
|
import ObjectHeaterIcon from "products/Construction/ObjectHeaterIcon";
|
||||||
|
import { pond, quack } from "protobuf-ts/pond";
|
||||||
|
import { useGlobalState, useMutationAPI } from "providers";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import FuelDark from "assets/components/fuelDark.png";
|
||||||
|
import FuelLight from "assets/components/fuelLight.png";
|
||||||
|
//import { useHistory } from "react-router";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import Link from "@mui/icons-material/Link";
|
||||||
|
import EventBlocker from "common/EventBlocker";
|
||||||
|
import { GetDeviceProductIcon } from "products/DeviceProduct";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
heaterData: pond.ObjectHeaterData;
|
||||||
|
linkDevice: (heater: ObjectHeater, linkedDevices: pond.ComprehensiveDevice[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
avatarIcon: {
|
||||||
|
width: theme.spacing(3),
|
||||||
|
height: theme.spacing(3)
|
||||||
|
},
|
||||||
|
listAvatar: {
|
||||||
|
minWidth: theme.spacing(5)
|
||||||
|
},
|
||||||
|
avatarImage: {
|
||||||
|
width: theme.spacing(3),
|
||||||
|
height: theme.spacing(3)
|
||||||
|
},
|
||||||
|
textOverflow: {
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
textTransform: "capitalize"
|
||||||
|
},
|
||||||
|
listText: {
|
||||||
|
margin: 0,
|
||||||
|
height: "100%"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function ObjectHeaterCard(props: Props) {
|
||||||
|
const { heaterData, linkDevice } = props;
|
||||||
|
const [heater, setHeater] = useState<ObjectHeater>(ObjectHeater.create());
|
||||||
|
const [connectedDevice, setConnectedDevice] = useState<Device>();
|
||||||
|
const mutationAPI = useMutationAPI();
|
||||||
|
const [heaterComponent, setHeaterComponent] = useState<Component>();
|
||||||
|
const [heaterTempComponent, setHeaterTempComponent] = useState<Component>();
|
||||||
|
const [siteTempComponent, setSiteTempComponent] = useState<Component>();
|
||||||
|
const [mutationsLoading, setMutationsLoading] = useState(false);
|
||||||
|
const classes = useStyles();
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const [{ user }] = useGlobalState();
|
||||||
|
const [lastFuelCalc, setLastFuelCalc] = useState<pond.ValueAt>();
|
||||||
|
const [lastFuelMutation, setLastFuelMutation] = useState<pond.LinearMutation>();
|
||||||
|
const [allComponentsRecent, setAllComponentsRecent] = useState(true);
|
||||||
|
//const history = useHistory();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let heater = ObjectHeater.any(heaterData.heater);
|
||||||
|
setHeater(heater);
|
||||||
|
if (!mutationsLoading && heater) {
|
||||||
|
let fuelMutation: pond.LinearMutation | undefined;
|
||||||
|
heater.settings.mutations.forEach(mutation => {
|
||||||
|
if (mutation.mutation === pond.Mutator.MUTATOR_FUEL_LEVEL) {
|
||||||
|
fuelMutation = mutation;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (fuelMutation !== undefined) {
|
||||||
|
mutationAPI
|
||||||
|
.linearMutation(
|
||||||
|
fuelMutation,
|
||||||
|
GetDefaultDateRange()
|
||||||
|
.end.add(-1, "hour")
|
||||||
|
.toISOString(),
|
||||||
|
GetDefaultDateRange().end.toISOString()
|
||||||
|
)
|
||||||
|
.then(resp => {
|
||||||
|
resp.data.values.forEach(val => {
|
||||||
|
setLastFuelCalc(val);
|
||||||
|
});
|
||||||
|
setLastFuelMutation(fuelMutation);
|
||||||
|
setMutationsLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (heaterData.devices[0]) {
|
||||||
|
setConnectedDevice(Device.any(heaterData.devices[0].device));
|
||||||
|
heaterData.devices[0].components.forEach(c => {
|
||||||
|
//loop through the components and assign them accordingly
|
||||||
|
let component = Component.any(c);
|
||||||
|
if (
|
||||||
|
component.type() === quack.ComponentType.COMPONENT_TYPE_BOOLEAN_OUTPUT &&
|
||||||
|
component.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER
|
||||||
|
) {
|
||||||
|
setHeaterComponent(component);
|
||||||
|
component.lastMeasurement.forEach(um => {
|
||||||
|
if (
|
||||||
|
um.timestamps.length > 0 &&
|
||||||
|
um.type === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
moment().diff(moment(um.timestamps[0]), "milliseconds") >
|
||||||
|
component.settings.reportPeriodMs
|
||||||
|
) {
|
||||||
|
setAllComponentsRecent(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (component.type() === quack.ComponentType.COMPONENT_TYPE_TEMPERATURE) {
|
||||||
|
setHeaterTempComponent(component);
|
||||||
|
component.lastMeasurement.forEach(um => {
|
||||||
|
if (
|
||||||
|
um.timestamps.length > 0 &&
|
||||||
|
um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
moment().diff(moment(um.timestamps[0]), "milliseconds") >
|
||||||
|
component.settings.reportPeriodMs
|
||||||
|
) {
|
||||||
|
setAllComponentsRecent(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (component.type() === quack.ComponentType.COMPONENT_TYPE_DHT) {
|
||||||
|
setSiteTempComponent(component);
|
||||||
|
component.lastMeasurement.forEach(um => {
|
||||||
|
if (
|
||||||
|
um.timestamps.length > 0 &&
|
||||||
|
um.type === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
moment().diff(moment(um.timestamps[0]), "milliseconds") >
|
||||||
|
component.settings.reportPeriodMs
|
||||||
|
) {
|
||||||
|
setAllComponentsRecent(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [heaterData, mutationAPI]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
const goToHeater = () => {
|
||||||
|
//history.push("/objectHeaters/" + heater.key);
|
||||||
|
let path = "/objectHeaters/" + heater.key;
|
||||||
|
navigate(path, { state: {heater: heater} });
|
||||||
|
};
|
||||||
|
|
||||||
|
const componentDisplay = (component?: Component) => {
|
||||||
|
return (
|
||||||
|
<Grid container direction="row" wrap="nowrap" alignContent="center" alignItems="center">
|
||||||
|
{component ? (
|
||||||
|
<React.Fragment>
|
||||||
|
<Grid>
|
||||||
|
<Avatar
|
||||||
|
variant="square"
|
||||||
|
alt="icon"
|
||||||
|
src={GetComponentIcon(component.type(), component.subType(), themeType)}
|
||||||
|
style={{ float: "left" }}
|
||||||
|
className={classes.avatarIcon}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid style={{ paddingLeft: 5 }}>
|
||||||
|
<UnitMeasurementSummary
|
||||||
|
component={component}
|
||||||
|
reading={UnitMeasurement.convertLastMeasurement(
|
||||||
|
component.lastMeasurement.map(um => UnitMeasurement.any(um, user))
|
||||||
|
)}
|
||||||
|
omitTime
|
||||||
|
dense
|
||||||
|
centered
|
||||||
|
onlyRecent
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</React.Fragment>
|
||||||
|
) : (
|
||||||
|
"Component Not Found"
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//// TODO-CS: Make a mutator describer to put the get functions into ////
|
||||||
|
const getMutationIcon = (mutation: pond.Mutator, themeType: string) => {
|
||||||
|
if (mutation === pond.Mutator.MUTATOR_FUEL_LEVEL) {
|
||||||
|
return themeType === "light" ? FuelDark : FuelLight;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMutationColour = (mutation: pond.Mutator) => {
|
||||||
|
if (mutation === pond.Mutator.MUTATOR_FUEL_LEVEL) {
|
||||||
|
return "lightBlue";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMutationUnit = (mutation: pond.Mutator) => {
|
||||||
|
if (mutation === pond.Mutator.MUTATOR_FUEL_LEVEL) {
|
||||||
|
return "%";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
//// /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ ////
|
||||||
|
|
||||||
|
const fuelMutationDisplay = () => {
|
||||||
|
if (!lastFuelMutation) {
|
||||||
|
return (
|
||||||
|
<Grid container direction="row" wrap="nowrap" alignContent="center" alignItems="center">
|
||||||
|
{/* <Grid item>
|
||||||
|
<Avatar
|
||||||
|
variant="square"
|
||||||
|
alt="icon"
|
||||||
|
src={getMutationIcon(pond.Mutator.MUTATOR_FUEL_LEVEL, themeType)}
|
||||||
|
style={{ float: "left" }}
|
||||||
|
className={classes.avatarIcon}
|
||||||
|
/>
|
||||||
|
</Grid> */}
|
||||||
|
<Grid style={{ paddingLeft: 5 }}>
|
||||||
|
N/A
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (lastFuelCalc) {
|
||||||
|
return (
|
||||||
|
<Grid container>
|
||||||
|
<Grid>
|
||||||
|
<Avatar
|
||||||
|
variant="square"
|
||||||
|
alt="icon"
|
||||||
|
src={getMutationIcon(pond.Mutator.MUTATOR_FUEL_LEVEL, themeType)}
|
||||||
|
style={{ float: "left" }}
|
||||||
|
className={classes.avatarIcon}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid style={{ paddingLeft: 5 }}>
|
||||||
|
<Typography variant="caption" color="textPrimary">
|
||||||
|
<span>
|
||||||
|
<span style={{ color: getMutationColour(pond.Mutator.MUTATOR_FUEL_LEVEL) }}>
|
||||||
|
{(lastFuelCalc.value / 10).toFixed(2) +
|
||||||
|
getMutationUnit(pond.Mutator.MUTATOR_FUEL_LEVEL)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Grid container direction="row" wrap="nowrap" alignContent="center" alignItems="center">
|
||||||
|
<Grid>
|
||||||
|
<Avatar
|
||||||
|
variant="square"
|
||||||
|
alt="icon"
|
||||||
|
src={getMutationIcon(pond.Mutator.MUTATOR_FUEL_LEVEL, themeType)}
|
||||||
|
style={{ float: "left" }}
|
||||||
|
className={classes.avatarIcon}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid style={{ paddingLeft: 5 }}>
|
||||||
|
No Recent Data
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//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);
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Box height="100%" display="flex" alignItems="center" flexWrap="nowrap" padding={2}>
|
||||||
|
<ListItemAvatar className={classes.listAvatar}>
|
||||||
|
<Avatar
|
||||||
|
variant="square"
|
||||||
|
src={productIcon}
|
||||||
|
className={classes.avatarImage}
|
||||||
|
alt="product"
|
||||||
|
/>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<Grid container direction="column">
|
||||||
|
<Grid>
|
||||||
|
<ListItemText
|
||||||
|
primary={heater.name}
|
||||||
|
primaryTypographyProps={{
|
||||||
|
className: classes.textOverflow,
|
||||||
|
variant: "subtitle1"
|
||||||
|
}}
|
||||||
|
className={classes.listText}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<ListItemText
|
||||||
|
primary={"Device: " + device.id()}
|
||||||
|
primaryTypographyProps={{
|
||||||
|
className: classes.textOverflow,
|
||||||
|
variant: "subtitle2"
|
||||||
|
}}
|
||||||
|
className={classes.listText}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<ListItemText
|
||||||
|
primary={"Last Checked In: " + moment(device.status.lastActive).fromNow()}
|
||||||
|
primaryTypographyProps={{
|
||||||
|
className: classes.textOverflow,
|
||||||
|
variant: "subtitle2"
|
||||||
|
}}
|
||||||
|
className={classes.listText}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
{allComponentsRecent ? (
|
||||||
|
<CardContent>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="row"
|
||||||
|
alignContent="center"
|
||||||
|
alignItems="center">
|
||||||
|
<Grid>{componentDisplay(heaterComponent)}</Grid>
|
||||||
|
<Grid>{componentDisplay(heaterTempComponent)}</Grid>
|
||||||
|
<Grid>{componentDisplay(siteTempComponent)}</Grid>
|
||||||
|
<Grid>{fuelMutationDisplay()}</Grid>
|
||||||
|
</Grid>
|
||||||
|
</CardContent>
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
style={{ backgroundColor: "orange", padding: 10, borderRadius: "0px 0px 10px 10px" }}>
|
||||||
|
A Component Is Not Reporting
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card raised onClick={goToHeater} style={{ margin: 5 }}>
|
||||||
|
{connectedDevice ? linkedCard(connectedDevice) : unlinkedCard()}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
214
src/pages/Site.tsx
Normal file
214
src/pages/Site.tsx
Normal file
|
|
@ -0,0 +1,214 @@
|
||||||
|
import { Card, Grid2 as Grid, Theme, Typography } from "@mui/material";
|
||||||
|
import DeviceLinkDrawer from "common/DeviceLinkDrawer";
|
||||||
|
import MapCard from "common/MapCard";
|
||||||
|
import ObjectControls from "common/ObjectControls";
|
||||||
|
import { useUserAPI, useMobile } from "hooks";
|
||||||
|
import SiteActions from "jobsite/SiteActions";
|
||||||
|
import { Device, Site as ISite, siteScope, teamScope } from "models";
|
||||||
|
import { ObjectHeater } from "models/ObjectHeater";
|
||||||
|
import ObjectHeaterCard from "objectHeater/ObjectHeaterCard";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useGlobalState, useObjectHeaterAPI, useSiteAPI } from "providers";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
//import { Redirect, useHistory, useRouteMatch } from "react-router";
|
||||||
|
import ObjectUsers from "user/ObjectUsers";
|
||||||
|
import PageContainer from "./PageContainer";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
card: {
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
height: 520,
|
||||||
|
overflowY: "scroll"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function Site() {
|
||||||
|
//const location = useLocation();
|
||||||
|
//const match = useRouteMatch<MatchParams>();
|
||||||
|
//const siteKey = match.params.siteID;
|
||||||
|
const siteKey = useParams<{ siteKey: string }>()?.siteKey ?? "";
|
||||||
|
//const history = useHistory();
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const classes = useStyles();
|
||||||
|
const [site, setSite] = useState<ISite>();
|
||||||
|
const [devices, setDevices] = useState<Map<string, pond.ComprehensiveDevice>>(
|
||||||
|
new Map<string, pond.ComprehensiveDevice>()
|
||||||
|
);
|
||||||
|
const [heaters, setHeaters] = useState<pond.ObjectHeaterData[]>([]);
|
||||||
|
//const deviceAPI = useDeviceAPI();
|
||||||
|
const userAPI = useUserAPI();
|
||||||
|
const siteAPI = useSiteAPI();
|
||||||
|
const [{ as, user }] = useGlobalState();
|
||||||
|
const [openLinkDrawer, setOpenLinkDrawer] = useState(false);
|
||||||
|
const [permissions, setPermissions] = useState<pond.Permission[]>([]);
|
||||||
|
const [loadingSite, setLoadingSite] = useState(false);
|
||||||
|
const isMobile = useMobile();
|
||||||
|
const [heaterToLink, setHeaterToLink] = useState<ObjectHeater>();
|
||||||
|
const objectHeaterAPI = useObjectHeaterAPI();
|
||||||
|
|
||||||
|
const loadSitePageData = useCallback(() => {
|
||||||
|
if (!loadingSite) {
|
||||||
|
setLoadingSite(true);
|
||||||
|
siteAPI
|
||||||
|
.getSitePage(siteKey)
|
||||||
|
.then(resp => {
|
||||||
|
setSite(ISite.any(resp.data.site));
|
||||||
|
setHeaters(resp.data.heaterData);
|
||||||
|
setLoadingSite(false);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [siteAPI]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadSitePageData();
|
||||||
|
let scope = siteScope(siteKey);
|
||||||
|
if (as) {
|
||||||
|
scope = teamScope(as);
|
||||||
|
}
|
||||||
|
userAPI.getUser(user.id(), scope).then(resp => {
|
||||||
|
setPermissions(resp.permissions);
|
||||||
|
});
|
||||||
|
}, [as, user, userAPI, loadSitePageData]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
const goToMap = () => {
|
||||||
|
navigate("/constructionsitemap", { state: {
|
||||||
|
long: site?.longitude(),
|
||||||
|
lat: site?.latitude()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const mobileView = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{heaters.map(h => (
|
||||||
|
<ObjectHeaterCard
|
||||||
|
key={h.heater?.key}
|
||||||
|
heaterData={h}
|
||||||
|
linkDevice={(heater, linkedDevices) => {
|
||||||
|
setHeaterToLink(heater);
|
||||||
|
setOpenLinkDrawer(true);
|
||||||
|
let d = new Map<string, pond.ComprehensiveDevice>();
|
||||||
|
linkedDevices.forEach(compDev => {
|
||||||
|
let device = Device.any(compDev.device);
|
||||||
|
d.set(device.id().toString(), compDev);
|
||||||
|
});
|
||||||
|
setDevices(d);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const desktopView = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Grid container direction="row" spacing={2} style={{ padding: 10 }}>
|
||||||
|
<Grid size={4}>
|
||||||
|
<Card className={classes.card}>
|
||||||
|
{heaters.map(h => (
|
||||||
|
<ObjectHeaterCard
|
||||||
|
key={h.heater?.key}
|
||||||
|
heaterData={h}
|
||||||
|
linkDevice={(heater, linkedDevices) => {
|
||||||
|
setHeaterToLink(heater);
|
||||||
|
setOpenLinkDrawer(true);
|
||||||
|
let d = new Map<string, pond.ComprehensiveDevice>();
|
||||||
|
linkedDevices.forEach(compDev => {
|
||||||
|
let device = Device.any(compDev.device);
|
||||||
|
d.set(device.id().toString(), compDev);
|
||||||
|
});
|
||||||
|
setDevices(d);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={4}>
|
||||||
|
{site && (
|
||||||
|
<MapCard
|
||||||
|
key={"map"}
|
||||||
|
path={[{ latitude: site.latitude(), longitude: site.longitude(), timestamp: "" }]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
<Grid size={4}>
|
||||||
|
<Card className={classes.card}>
|
||||||
|
{site && (
|
||||||
|
<ObjectUsers
|
||||||
|
scope={siteScope(site.key())}
|
||||||
|
label={"Users For " + site.siteName()}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={true}
|
||||||
|
refreshCallback={() => {}}
|
||||||
|
closeDialogCallback={() => {}}
|
||||||
|
cardMode
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<DeviceLinkDrawer
|
||||||
|
open={openLinkDrawer}
|
||||||
|
close={() => {
|
||||||
|
setOpenLinkDrawer(false);
|
||||||
|
}}
|
||||||
|
linkedDevices={devices}
|
||||||
|
updateLinkedDevices={(dev, linked) => {
|
||||||
|
let deviceID = dev.device?.settings?.deviceId;
|
||||||
|
if (deviceID && heaterToLink) {
|
||||||
|
if (linked) {
|
||||||
|
objectHeaterAPI
|
||||||
|
.updateLink(heaterToLink.key, "objectHeater", deviceID.toString(), "device", [
|
||||||
|
"read",
|
||||||
|
"write",
|
||||||
|
"grant",
|
||||||
|
"revoke"
|
||||||
|
])
|
||||||
|
.then(resp => {
|
||||||
|
if (deviceID) {
|
||||||
|
devices.set(deviceID.toString(), dev);
|
||||||
|
setOpenLinkDrawer(false);
|
||||||
|
loadSitePageData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ObjectControls
|
||||||
|
actions={
|
||||||
|
<SiteActions
|
||||||
|
site={site ?? ISite.create()}
|
||||||
|
permissions={permissions}
|
||||||
|
refreshCallback={() => {}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
mapFunction={goToMap}
|
||||||
|
// linkDeviceFunction={() => {
|
||||||
|
// setOpenLinkDrawer(true);
|
||||||
|
// }}
|
||||||
|
teamScope={isMobile && site ? siteScope(site.key()) : undefined}
|
||||||
|
permissions={permissions}
|
||||||
|
/>
|
||||||
|
<Typography variant="h5" style={{ margin: 10, fontWeight: 650 }}>
|
||||||
|
{site?.siteName()}
|
||||||
|
</Typography>
|
||||||
|
{isMobile ? mobileView() : desktopView()}
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
306
src/pages/Sites.tsx
Normal file
306
src/pages/Sites.tsx
Normal file
|
|
@ -0,0 +1,306 @@
|
||||||
|
import { Avatar, Box, Card, Grid2 as Grid, Switch, Tab, Tabs, Tooltip, Typography } from "@mui/material";
|
||||||
|
import { Group } from "@mui/icons-material";
|
||||||
|
//import MaterialTable from "material-table";
|
||||||
|
import { Site, User } from "models";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useGlobalState, useSiteAPI } from "providers";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import PageContainer from "./PageContainer";
|
||||||
|
//import { getTableIcons } from "common/ResponsiveTable";
|
||||||
|
//import { useHistory } from "react-router";
|
||||||
|
import { useMobile } from "hooks";
|
||||||
|
import JobsiteIcon from "products/Construction/JobSiteIcon";
|
||||||
|
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
interface SiteWithData {
|
||||||
|
site: Site;
|
||||||
|
users: User[];
|
||||||
|
heaterData: pond.ObjectHeaterData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TabPanelProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
index: any;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Sites() {
|
||||||
|
const siteAPI = useSiteAPI();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [{ as }] = useGlobalState();
|
||||||
|
//const history = useHistory();
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const [sites, setSites] = useState<SiteWithData[]>([]);
|
||||||
|
const isMobile = useMobile();
|
||||||
|
const [currentTab, setCurrentTab] = useState(0);
|
||||||
|
const [tablePage, setTablePage] = useState(0);
|
||||||
|
const [pageSize, setPageSize] = useState(10);
|
||||||
|
const [total, setTotal] = useState(0)
|
||||||
|
|
||||||
|
const loadSites = useCallback(() => {
|
||||||
|
if (loading) return;
|
||||||
|
setLoading(true);
|
||||||
|
//make a site api that gets an array of objects that contain the site, its devices, and teams
|
||||||
|
siteAPI
|
||||||
|
.listSitesPageData(pageSize, tablePage * pageSize, "asc", "siteName", undefined, as)
|
||||||
|
.then(resp => {
|
||||||
|
console.log(resp)
|
||||||
|
setTotal(resp.data.total);
|
||||||
|
buildTableData(resp.data.sites);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}, [as, siteAPI, pageSize, tablePage]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
const buildTableData = (siteData: pond.SiteData[]) => {
|
||||||
|
let data: SiteWithData[] = [];
|
||||||
|
siteData.forEach(site => {
|
||||||
|
if (site.site?.settings?.siteName) {
|
||||||
|
let newTableData: SiteWithData = {
|
||||||
|
site: Site.any(site.site),
|
||||||
|
users: [],
|
||||||
|
heaterData: []
|
||||||
|
};
|
||||||
|
if (site.users) {
|
||||||
|
newTableData.users = site.users.map(u => User.any(u));
|
||||||
|
}
|
||||||
|
if (site.heaterData) {
|
||||||
|
newTableData.heaterData = site.heaterData;
|
||||||
|
}
|
||||||
|
data.push(newTableData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setSites([...data]);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadSites();
|
||||||
|
}, [loadSites]);
|
||||||
|
|
||||||
|
const siteInfoDisplay = (site: SiteWithData) => {
|
||||||
|
return (
|
||||||
|
<Grid container direction="column">
|
||||||
|
<Grid>
|
||||||
|
<Typography style={{ fontWeight: 650 }}>{site.site.siteName()}</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
{site.heaterData.length + (site.heaterData.length > 1 ? " Heaters" : " Heater")}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToSite = (jobsite: SiteWithData) => {
|
||||||
|
// let path = "/jobsites/" + jobsite.site.settings.key;
|
||||||
|
// history.push(path, {});
|
||||||
|
let path = "/jobsites/" + jobsite.site.key();
|
||||||
|
navigate(path, { state: jobsite.site })
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateActive = (active: boolean, site: Site) => {
|
||||||
|
site.settings.active = active;
|
||||||
|
siteAPI
|
||||||
|
.updateSite(site.key(), site.settings)
|
||||||
|
.then(resp => {})
|
||||||
|
.catch(err => {});
|
||||||
|
setSites([...sites]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const desktopColumns = (): Column<SiteWithData>[] => {
|
||||||
|
return ([
|
||||||
|
{
|
||||||
|
title: "Site",
|
||||||
|
render: row => (
|
||||||
|
<Box padding={2}>
|
||||||
|
<Typography>
|
||||||
|
{row.site.siteName()}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Heaters",
|
||||||
|
render: row => (
|
||||||
|
<Grid container spacing={1}>
|
||||||
|
{row.heaterData.map((hd: pond.ObjectHeaterData) => (
|
||||||
|
<Grid key={hd.heater?.key}>
|
||||||
|
{hd.heater?.name}
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Users",
|
||||||
|
render: row => (
|
||||||
|
<Grid container spacing={1}>
|
||||||
|
{row.users.map((user: User) => (
|
||||||
|
<Grid key={user.id()}>
|
||||||
|
<Tooltip title={user.name()}>
|
||||||
|
{user.settings.avatar ? (
|
||||||
|
<Avatar alt={user.settings.name} src={user.settings.avatar} />
|
||||||
|
) : (
|
||||||
|
<Avatar alt={user.settings.name}>
|
||||||
|
<Group />
|
||||||
|
</Avatar>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Active",
|
||||||
|
render: row => (
|
||||||
|
<React.Fragment>
|
||||||
|
<Switch
|
||||||
|
checked={row.site.settings.active}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
onChange={e => {
|
||||||
|
updateActive(e.target.checked, row.site);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTablePage = (event: any) => {
|
||||||
|
setPageSize(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const desktopView = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveTable<SiteWithData>
|
||||||
|
title={"Jobsites"}
|
||||||
|
page={tablePage}
|
||||||
|
pageSize={pageSize}
|
||||||
|
columns={desktopColumns}
|
||||||
|
setPage={(newPage) => {setTablePage(newPage)}}
|
||||||
|
total={total}
|
||||||
|
rows={sites}
|
||||||
|
handleRowsPerPageChange={handleTablePage}
|
||||||
|
onRowClick={goToSite}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
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 handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
|
||||||
|
setCurrentTab(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mobileView = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Tabs
|
||||||
|
value={currentTab}
|
||||||
|
indicatorColor="primary"
|
||||||
|
textColor="primary"
|
||||||
|
onChange={handleChange}
|
||||||
|
centered
|
||||||
|
style={{
|
||||||
|
marginLeft: "auto"
|
||||||
|
}}>
|
||||||
|
<Tab label="Active" />
|
||||||
|
<Tab label="Inactive" />
|
||||||
|
</Tabs>
|
||||||
|
<TabPanelMine value={currentTab} index={0}>
|
||||||
|
{sites.map(site => {
|
||||||
|
let siteCards: JSX.Element[] = [];
|
||||||
|
if (site.site.settings.active) {
|
||||||
|
siteCards.push(
|
||||||
|
<Card
|
||||||
|
key={site.site.key()}
|
||||||
|
style={{ margin: 10, padding: 5 }}
|
||||||
|
onClick={() => {
|
||||||
|
goToSite(site);
|
||||||
|
}}>
|
||||||
|
<Grid container alignContent="center" alignItems="center">
|
||||||
|
<Grid size={2} style={{ textAlign: "center" }}>
|
||||||
|
<JobsiteIcon size={50} />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={7}>
|
||||||
|
{/* information (site name, number of devices, teams in site) */}
|
||||||
|
{siteInfoDisplay(site)}
|
||||||
|
</Grid>
|
||||||
|
<Grid size={3}>
|
||||||
|
<Switch
|
||||||
|
defaultChecked={site.site.settings.active}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
onChange={e => {
|
||||||
|
updateActive(e.target.checked, site.site);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return siteCards;
|
||||||
|
})}
|
||||||
|
</TabPanelMine>
|
||||||
|
<TabPanelMine value={currentTab} index={1}>
|
||||||
|
{sites.map(site => {
|
||||||
|
let siteCards: JSX.Element[] = [];
|
||||||
|
if (!site.site.settings.active) {
|
||||||
|
siteCards.push(
|
||||||
|
<Card
|
||||||
|
key={site.site.key()}
|
||||||
|
style={{ margin: 10, padding: 5 }}
|
||||||
|
onClick={() => {
|
||||||
|
goToSite(site);
|
||||||
|
}}>
|
||||||
|
<Grid container alignContent="center" alignItems="center">
|
||||||
|
<Grid size={2} style={{ textAlign: "center" }}>
|
||||||
|
<JobsiteIcon size={50} />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={7}>
|
||||||
|
{/* information (site name, number of devices, teams in site) */}
|
||||||
|
{siteInfoDisplay(site)}
|
||||||
|
</Grid>
|
||||||
|
<Grid size={3}>
|
||||||
|
<Switch
|
||||||
|
defaultChecked={site.site.settings.active}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
onChange={e => {
|
||||||
|
updateActive(e.target.checked, site.site);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return siteCards;
|
||||||
|
})}
|
||||||
|
</TabPanelMine>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <PageContainer>{isMobile ? mobileView() : desktopView()}</PageContainer>;
|
||||||
|
}
|
||||||
|
|
@ -38,8 +38,8 @@ export {
|
||||||
// useKeyManagerAPI,
|
// useKeyManagerAPI,
|
||||||
useTerminalAPI, //TODO: update api with resolve,reject
|
useTerminalAPI, //TODO: update api with resolve,reject
|
||||||
useGateAPI,
|
useGateAPI,
|
||||||
// useObjectHeaterAPI,
|
useObjectHeaterAPI, //TODO: update api with resolve,reject
|
||||||
// useMutationAPI,
|
useMutationAPI, //TODO: update api with resolve,reject
|
||||||
useGrainBagAPI,
|
useGrainBagAPI,
|
||||||
useTransactionAPI,
|
useTransactionAPI,
|
||||||
useFileControllerAPI
|
useFileControllerAPI
|
||||||
|
|
|
||||||
194
src/providers/pond/ObjectHeaterAPI.tsx
Normal file
194
src/providers/pond/ObjectHeaterAPI.tsx
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import { useHTTP } from "hooks";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useGlobalState } from "providers";
|
||||||
|
import React, { createContext, PropsWithChildren, useContext } from "react";
|
||||||
|
import { or } from "utils";
|
||||||
|
import { pondURL } from "./pond";
|
||||||
|
|
||||||
|
export interface IObjectHeaterAPIContext {
|
||||||
|
addObjectHeater: (
|
||||||
|
name: string,
|
||||||
|
heater: pond.ObjectHeaterSettings
|
||||||
|
) => Promise<AxiosResponse<pond.AddObjectHeaterResponse>>;
|
||||||
|
getObjectHeater: (key: string) => Promise<AxiosResponse<pond.GetObjectHeaterResponse>>;
|
||||||
|
listObjectHeaters: (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
asRoot?: boolean,
|
||||||
|
as?: string,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[],
|
||||||
|
numerical?: boolean
|
||||||
|
) => Promise<AxiosResponse<pond.ListObjectHeatersResponse>>;
|
||||||
|
removeObjectHeater: (key: string) => Promise<AxiosResponse<pond.RemoveObjectHeaterResponse>>;
|
||||||
|
updateObjectHeater: (
|
||||||
|
key: string,
|
||||||
|
name: string,
|
||||||
|
settings: pond.ObjectHeaterSettings,
|
||||||
|
asRoot?: true
|
||||||
|
) => Promise<AxiosResponse<pond.UpdateObjectHeaterResponse>>;
|
||||||
|
updateLink: (
|
||||||
|
parentKey: string,
|
||||||
|
parentType: string,
|
||||||
|
objectID: string,
|
||||||
|
objectType: string,
|
||||||
|
permissions: string[]
|
||||||
|
) => Promise<any>;
|
||||||
|
listObjectHeatersPageData: (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
as?: string,
|
||||||
|
asRoot?: boolean
|
||||||
|
) => Promise<AxiosResponse<pond.ListObjectHeatersPageDataResponse>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ObjectHeaterAPIContext = createContext<IObjectHeaterAPIContext>(
|
||||||
|
{} as IObjectHeaterAPIContext
|
||||||
|
);
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
export default function ObjectHeaterProvider(props: PropsWithChildren<Props>) {
|
||||||
|
const { children } = props;
|
||||||
|
const { get, del, post, put } = useHTTP();
|
||||||
|
const [{ as }] = useGlobalState();
|
||||||
|
|
||||||
|
const addObjectHeater = (name: string, settings: pond.ObjectHeaterSettings) => {
|
||||||
|
if (as)
|
||||||
|
return post<pond.AddObjectHeaterResponse>(
|
||||||
|
pondURL("/objectHeaters?name=" + name + "&as=" + as),
|
||||||
|
settings
|
||||||
|
);
|
||||||
|
return post<pond.AddObjectHeaterResponse>(pondURL("/objectHeaters?name=" + name), settings);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getObjectHeater = (key: string) => {
|
||||||
|
if (as) {
|
||||||
|
return get<pond.GetObjectHeaterResponse>(pondURL("/objectHeaters/" + key + "?as=" + as));
|
||||||
|
}
|
||||||
|
return get<pond.GetObjectHeaterResponse>(pondURL("/objectHeaters/" + key));
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeObjectHeater = (key: string) => {
|
||||||
|
if (as)
|
||||||
|
return del<pond.RemoveObjectHeaterResponse>(pondURL("/objectHeaters/" + key + "?as=" + as));
|
||||||
|
return del<pond.RemoveObjectHeaterResponse>(pondURL("/objectHeaters/" + key));
|
||||||
|
};
|
||||||
|
|
||||||
|
const listObjectHeaters = (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
asRoot?: boolean,
|
||||||
|
as?: string,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[],
|
||||||
|
numerical?: boolean
|
||||||
|
) => {
|
||||||
|
return get<pond.ListObjectHeatersResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/objectHeaters" +
|
||||||
|
"?limit=" +
|
||||||
|
limit +
|
||||||
|
"&offset=" +
|
||||||
|
offset +
|
||||||
|
("&order=" + (order ? order : "asc")) +
|
||||||
|
("&by=" + (orderBy ? orderBy : "key")) +
|
||||||
|
(search ? "&search=" + search : "") +
|
||||||
|
(numerical ? "&numerical=true" : "") +
|
||||||
|
(asRoot ? "&asRoot=" + asRoot.toString() : "") +
|
||||||
|
(as ? "&as=" + as : "") +
|
||||||
|
(keys ? "&keys=" + keys.toString() : "") +
|
||||||
|
(types ? "&types=" + types.toString() : "")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateObjectHeater = (key: string, name: string, settings: pond.ObjectHeaterSettings) => {
|
||||||
|
if (as) {
|
||||||
|
return put<pond.UpdateObjectHeaterResponse>(
|
||||||
|
pondURL("/objectHeaters/" + key + "?as=" + as + "&name=" + name),
|
||||||
|
settings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return put<pond.UpdateObjectHeaterResponse>(
|
||||||
|
pondURL("/objectHeaters/" + key + "?name=" + name),
|
||||||
|
settings
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateLink = (
|
||||||
|
parentID: string,
|
||||||
|
parentType: string,
|
||||||
|
objectID: string,
|
||||||
|
objectType: string,
|
||||||
|
permissions: string[]
|
||||||
|
) => {
|
||||||
|
if (as)
|
||||||
|
return post(pondURL(`/objectHeaters/` + parentID + `/link?as=${as}`), {
|
||||||
|
Key: objectID,
|
||||||
|
Type: objectType,
|
||||||
|
Parent: parentID,
|
||||||
|
ParentType: parentType,
|
||||||
|
Permissions: permissions
|
||||||
|
});
|
||||||
|
return post(pondURL(`/objectHeaters/` + parentID + `/link`), {
|
||||||
|
Key: objectID,
|
||||||
|
Type: objectType,
|
||||||
|
Parent: parentID,
|
||||||
|
ParentType: parentType,
|
||||||
|
Permissions: permissions
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const listObjectHeatersPageData = (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
as?: string,
|
||||||
|
asRoot?: boolean
|
||||||
|
) => {
|
||||||
|
return get<pond.ListObjectHeatersPageDataResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/objectHeatersWithData" +
|
||||||
|
"?limit=" +
|
||||||
|
limit +
|
||||||
|
"&offset=" +
|
||||||
|
offset +
|
||||||
|
("&order=" + or(order, "asc")) +
|
||||||
|
("&by=" + or(orderBy, "key")) +
|
||||||
|
(as ? "&as=" + as : "") +
|
||||||
|
(asRoot ? "&asRoot=" + asRoot.toString() : "") +
|
||||||
|
(search ? "&search=" + search : "")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ObjectHeaterAPIContext.Provider
|
||||||
|
value={{
|
||||||
|
addObjectHeater,
|
||||||
|
getObjectHeater,
|
||||||
|
listObjectHeaters,
|
||||||
|
removeObjectHeater,
|
||||||
|
updateObjectHeater,
|
||||||
|
updateLink,
|
||||||
|
listObjectHeatersPageData
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</ObjectHeaterAPIContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useObjectHeaterAPI = () => useContext(ObjectHeaterAPIContext);
|
||||||
48
src/providers/pond/mutationAPI.tsx
Normal file
48
src/providers/pond/mutationAPI.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { useHTTP } from "hooks";
|
||||||
|
import { createContext, PropsWithChildren, useContext } from "react";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { pondURL } from "./pond";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import { useGlobalState } from "providers";
|
||||||
|
|
||||||
|
export interface IMutationAPIContext {
|
||||||
|
linearMutation: (
|
||||||
|
linearMutation: pond.LinearMutation,
|
||||||
|
start: string,
|
||||||
|
end: string
|
||||||
|
) => Promise<AxiosResponse<pond.LinearMutationResponse>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MutationAPIContext = createContext<IMutationAPIContext>({} as IMutationAPIContext);
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
export default function MutationProvider(props: PropsWithChildren<Props>) {
|
||||||
|
const { children } = props;
|
||||||
|
const { post } = useHTTP();
|
||||||
|
//const permissionAPI = usePermissionAPI();
|
||||||
|
const [{ as }] = useGlobalState();
|
||||||
|
|
||||||
|
const linearMutation = (linearMutation: pond.LinearMutation, start: string, end: string) => {
|
||||||
|
if (as)
|
||||||
|
return post<pond.LinearMutationResponse>(
|
||||||
|
pondURL("/linearMutation?start=" + start + "&end=" + end + "&as=" + as),
|
||||||
|
linearMutation
|
||||||
|
);
|
||||||
|
return post<pond.LinearMutationResponse>(
|
||||||
|
pondURL("/linearMutation?start=" + start + "&end=" + end),
|
||||||
|
linearMutation
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MutationAPIContext.Provider
|
||||||
|
value={{
|
||||||
|
linearMutation
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</MutationAPIContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMutationAPI = () => useContext(MutationAPIContext);
|
||||||
|
|
@ -26,6 +26,8 @@ import HomeMarkerProvider, {useHomeMarkerAPI} from "./homeMarkerAPI";
|
||||||
import HarvestPlanProvider, {useHarvestPlanAPI} from "./harvestPlanAPI";
|
import HarvestPlanProvider, {useHarvestPlanAPI} from "./harvestPlanAPI";
|
||||||
import TerminalProvider, {useTerminalAPI} from "./terminalAPI"
|
import TerminalProvider, {useTerminalAPI} from "./terminalAPI"
|
||||||
import SiteProvider, {useSiteAPI} from "./siteAPI";
|
import SiteProvider, {useSiteAPI} from "./siteAPI";
|
||||||
|
import ObjectHeaterProvider, {useObjectHeaterAPI} from "./ObjectHeaterAPI";
|
||||||
|
import MutationProvider, {useMutationAPI} from "./mutationAPI"
|
||||||
// import NoteProvider from "providers/noteAPI";
|
// import NoteProvider from "providers/noteAPI";
|
||||||
|
|
||||||
export const pondURL = (partial: string, demo: boolean = false): string => {
|
export const pondURL = (partial: string, demo: boolean = false): string => {
|
||||||
|
|
@ -68,7 +70,11 @@ export default function PondProvider(props: PropsWithChildren<any>) {
|
||||||
<HarvestPlanProvider>
|
<HarvestPlanProvider>
|
||||||
<TerminalProvider>
|
<TerminalProvider>
|
||||||
<SiteProvider>
|
<SiteProvider>
|
||||||
|
<ObjectHeaterProvider>
|
||||||
|
<MutationProvider>
|
||||||
{children}
|
{children}
|
||||||
|
</MutationProvider>
|
||||||
|
</ObjectHeaterProvider>
|
||||||
</SiteProvider>
|
</SiteProvider>
|
||||||
</TerminalProvider>
|
</TerminalProvider>
|
||||||
</HarvestPlanProvider>
|
</HarvestPlanProvider>
|
||||||
|
|
@ -123,5 +129,7 @@ export {
|
||||||
useHomeMarkerAPI,
|
useHomeMarkerAPI,
|
||||||
useHarvestPlanAPI,
|
useHarvestPlanAPI,
|
||||||
useTerminalAPI,
|
useTerminalAPI,
|
||||||
useSiteAPI
|
useSiteAPI,
|
||||||
|
useObjectHeaterAPI,
|
||||||
|
useMutationAPI
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue