added the sites page and all other required imports for it to work, also fixed the map card

This commit is contained in:
csawatzky 2025-04-03 12:54:00 -06:00
parent c5afdc2c3d
commit 3990dfbf4f
14 changed files with 1595 additions and 21 deletions

214
src/pages/Site.tsx Normal file
View 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
View 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>;
}