frontend/src/pages/Sites.tsx
2025-05-26 16:25:38 -06:00

336 lines
10 KiB
TypeScript

import { Avatar, Box, Button, 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";
import { cloneDeep } from "lodash";
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 => {
setTotal(resp.data.total);
setSites(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);
}
});
return 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, undefined, as)
.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}
loadMore={()=>{
let current = cloneDeep(sites)
setLoading(true);
siteAPI
.listSitesPageData(pageSize, current.length, "asc", "siteName", undefined, as)
.then(resp => {
let newData = buildTableData(resp.data.sites);
setSites(current.concat(newData))
})
.finally(() => {
setLoading(false);
});
}}
/>
)
};
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 loadMore = () => {
let current = cloneDeep(sites)
setLoading(true);
siteAPI
.listSitesPageData(pageSize, current.length, "asc", "siteName", undefined, as)
.then(resp => {
let newData = buildTableData(resp.data.sites);
setSites(current.concat(newData))
})
.finally(() => {
setLoading(false);
});
}
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>
{sites.length < total &&
<Button onClick={loadMore} variant="contained" color="primary" fullWidth>Load More</Button>
}
</React.Fragment>
);
};
return <PageContainer>{isMobile ? mobileView() : desktopView()}</PageContainer>;
}