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
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>;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue