Merge branch 'table_load_more' into dev_environment

This commit is contained in:
csawatzky 2025-05-26 16:05:13 -06:00
commit b82c727580
14 changed files with 208 additions and 64 deletions

View file

@ -1,4 +1,4 @@
import { Box, Button, DialogActions, DialogContent, DialogTitle } from "@mui/material"; import { Box, Button, Checkbox, DialogActions, DialogContent, DialogTitle, Typography } from "@mui/material";
// import MaterialTable from "material-table"; // import MaterialTable from "material-table";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import ResponsiveDialog from "./ResponsiveDialog"; import ResponsiveDialog from "./ResponsiveDialog";
@ -36,7 +36,7 @@ export default function(props: Props) {
width: 500 width: 500
}, },
viewerMobile: { viewerMobile: {
height: "100%", height: 500,
width: "100%" width: "100%"
}, },
page: { page: {
@ -103,12 +103,12 @@ export default function(props: Props) {
selected.push(row) selected.push(row)
}else{ //remove the page/index from the map }else{ //remove the page/index from the map
if(pageSelect){ if(pageSelect){
pageSelect.splice(pageSelect.indexOf(index)) pageSelect.splice(pageSelect.indexOf(index), 1)
} }
//remove the selected object from the array //remove the selected object from the array
selected.forEach((codeKey, index) => { selected.forEach((codeKey, index) => {
if(row.key === codeKey.key){ if(row.key === codeKey.key){
selected.splice(index) selected.splice(index, 1)
} }
}) })
} }
@ -137,35 +137,27 @@ export default function(props: Props) {
total={keyList.length} total={keyList.length}
setPage={(page) => {setTablePage(page)}} setPage={(page) => {setTablePage(page)}}
handleRowsPerPageChange={handleRowsPerPageChange} handleRowsPerPageChange={handleRowsPerPageChange}
loadMore={() => {
let clone: QrCodeKey[] = cloneDeep(keyList)
let current = cloneDeep(displayedRows)
let newRows = clone.splice(current.length, pageSize)
setDisplayedRows(current.concat(newRows))
}}
renderMobile={(row, index) => {
return (
<Box display="flex">
<Checkbox
onChange={(e, checked) => {
rowSelected(row, index, checked)
}}
/>
<Typography>
{row.name}
</Typography>
</Box>
)
}}
/> />
{/* <MaterialTable
icons={getTableIcons()}
title={""}
data={keyList}
options={{
paging: true,
// pageSize: pageSize,
// pageSizeOptions: [5, 10, 20],
padding: "dense",
showTitle: true,
toolbar: true,
sorting: true,
columnsButton: true,
search: false,
selection: true
}}
onSelectionChange={(data, rowData) => {
setSelectedKeys(data);
}}
columns={[
{
cellStyle: {
padding: 0
},
title: "Name",
field: "name"
}
]}></MaterialTable> */}
</React.Fragment> </React.Fragment>
); );
}; };

View file

@ -85,7 +85,7 @@ interface Props<T> {
multiGutter?: boolean; multiGutter?: boolean;
rowsPerPageOptions?: number[]; rowsPerPageOptions?: number[];
noDataMessage?: string; noDataMessage?: string;
renderMobile?: (row: T) => JSX.Element; renderMobile?: (row: T, index: number) => JSX.Element;
hideKeys?: boolean; hideKeys?: boolean;
onRowClick?: (row: T) => void; onRowClick?: (row: T) => void;
actions?: string | JSX.Element; actions?: string | JSX.Element;
@ -273,9 +273,9 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
return String(val).charAt(0).toUpperCase() + String(val).slice(1); return String(val).charAt(0).toUpperCase() + String(val).slice(1);
} }
const renderMobileRow = (row: T) => { const renderMobileRow = (row: T, index: number) => {
if (renderMobile) return ( if (renderMobile) return (
renderMobile(row) renderMobile(row, index)
) )
if (columns) { if (columns) {
return ( return (
@ -330,7 +330,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
{rows.map((row, index) => { {rows.map((row, index) => {
return ( return (
<Card className={classNames(classes.card)} key={"mobile-card-"+index} onClick={() => onRowClick&&onRowClick(row)}> <Card className={classNames(classes.card)} key={"mobile-card-"+index} onClick={() => onRowClick&&onRowClick(row)}>
{renderMobileRow(row)} {renderMobileRow(row, index)}
{renderGutter && {renderGutter &&
<> <>
<Divider/> <Divider/>

View file

@ -369,6 +369,12 @@ export default function ContractTransactionTable(props: Props) {
total={tableData.length} total={tableData.length}
handleRowsPerPageChange={(e)=>{setPageSize(e.target.value)}} handleRowsPerPageChange={(e)=>{setPageSize(e.target.value)}}
setPage={(page)=>{setTablePage(page)}} setPage={(page)=>{setTablePage(page)}}
loadMore={()=>{
let clone: Transaction[] = cloneDeep(tableData)
let current = cloneDeep(displayData)
let newRows = clone.splice(current.length, pageSize)
setDisplayData(current.concat(newRows))
}}
/> />
{/* <MaterialTable {/* <MaterialTable
title={"Deliveries"} title={"Deliveries"}

View file

@ -127,6 +127,7 @@ export default function GateFlowGraph(props: Props) {
setRuntime(moment.duration(runtime)); setRuntime(moment.duration(runtime));
let state = false; let state = false;
if ( if (
data.length > 1 &&
data[data.length - 1].value > gate.lowerFlow() && data[data.length - 1].value > gate.lowerFlow() &&
data[data.length - 1].value < gate.upperFlow() data[data.length - 1].value < gate.upperFlow()
) { ) {

View file

@ -12,6 +12,7 @@ import AddGateFab from "./AddGateFab";
import GateSettings from "./GateSettings"; import GateSettings from "./GateSettings";
import { useGateAPI, useGlobalState } from "providers"; import { useGateAPI, useGlobalState } from "providers";
import { Settings } from "@mui/icons-material"; import { Settings } from "@mui/icons-material";
import { cloneDeep } from "lodash";
interface Props { interface Props {
//gates: Gate[]; //gates: Gate[];
@ -219,6 +220,19 @@ export default function GateList(props: Props) {
onRowClick={(gate) => {goToGate(gate)}} onRowClick={(gate) => {goToGate(gate)}}
hideKeys={isMobile || props.useMobile} hideKeys={isMobile || props.useMobile}
setSearchText={(search) => setSearch(search)} setSearchText={(search) => setSearch(search)}
loadMore={()=>{
let current = cloneDeep(gates)
let keys = undefined
let types = undefined
if(currentTerminal) {
keys = [currentTerminal]
types = ["terminal"]
}
gateAPI.listGates(pageSize, current.length , "asc", "name", search, undefined, keys, types, undefined, undefined, as).then(resp => {
let newGates = resp.data.gates.map(gate => Gate.create(gate))
setGates(current.concat(newGates))
})
}}
/> />
) )
}; };

View file

@ -3,6 +3,7 @@ import DisplayDrawer from "common/DisplayDrawer";
import ResponsiveTable from "common/ResponsiveTable"; import ResponsiveTable from "common/ResponsiveTable";
//import DeviceCardList from "device/DeviceCardList"; //import DeviceCardList from "device/DeviceCardList";
import { useGroupAPI, useSnackbar } from "hooks"; import { useGroupAPI, useSnackbar } from "hooks";
import { cloneDeep } from "lodash";
import { Group } from "models"; import { Group } from "models";
import moment from "moment"; import moment from "moment";
import { describePower } from "pbHelpers/Power"; import { describePower } from "pbHelpers/Power";
@ -28,18 +29,19 @@ export default function GroupDrawer(props: Props) {
const { openSnack } = useSnackbar(); const { openSnack } = useSnackbar();
const [grpDevs, setGrpDevs] = useState<pond.ComprehensiveDevice[]>([]); const [grpDevs, setGrpDevs] = useState<pond.ComprehensiveDevice[]>([]);
const navigate = useNavigate(); const navigate = useNavigate();
const [tablePage, setTablePage] = useState(0) const loadLimit = 10
const [pageSize, setPageSize] = useState(10) const [groupTotal, setGroupTotal] = useState(0)
const loadDevs = useCallback(() => { const loadDevs = useCallback(() => {
groupAPI groupAPI
.listGroupDevices(group.id(), 100, 0, "asc", undefined, true) .listGroupDevices(group.id(), loadLimit, 0, "asc", undefined, true)
.then(resp => { .then(resp => {
const groupDevices: pond.ComprehensiveDevice[] = or( const groupDevices: pond.ComprehensiveDevice[] = or(
resp.data.comprehensiveDevices, resp.data.comprehensiveDevices,
[] []
).map(d => pond.ComprehensiveDevice.fromObject(d)); ).map(d => pond.ComprehensiveDevice.fromObject(d));
setGrpDevs(groupDevices); setGrpDevs(groupDevices);
setGroupTotal(resp.data.total)
}) })
.catch(err => { .catch(err => {
openSnack("Failed to get devices for group"); openSnack("Failed to get devices for group");
@ -174,21 +176,37 @@ export default function GroupDrawer(props: Props) {
} }
const drawerBody = () => { const drawerBody = () => {
// TODO: will need to update the rows and setPage functions if we decide to have the table only show a number of items matching its page size rether than everything passed in
return ( return (
<Box> <Box>
<ResponsiveTable<pond.ComprehensiveDevice> <ResponsiveTable<pond.ComprehensiveDevice>
page={tablePage} page={0}
pageSize={pageSize} pageSize={loadLimit}
onRowClick={toDevice} onRowClick={toDevice}
handleRowsPerPageChange={()=>{}} total={groupTotal}
setPage={()=>{}}
total={0}
rows={grpDevs} rows={grpDevs}
renderMobile={mobile} renderMobile={mobile}
renderGutter={gutter} renderGutter={gutter}
gutterPadding={0} gutterPadding={0}
mobileView mobileView
loadMore={() => {
let current = cloneDeep(grpDevs)
groupAPI
.listGroupDevices(group.id(), loadLimit, current.length, "asc", undefined, true)
.then(resp => {
let newDevices: pond.ComprehensiveDevice[] = or(
resp.data.comprehensiveDevices,
[]
).map(d => pond.ComprehensiveDevice.fromObject(d));
setGrpDevs(current.concat(newDevices));
setGroupTotal(resp.data.total)
})
.catch(err => {
openSnack("Failed to get devices for group");
});
}}
//these two function are uneccessary as this will always be in a list as mobile view
handleRowsPerPageChange={()=>{}}
setPage={()=>{}}
/> />
</Box> </Box>
) )

View file

@ -371,7 +371,7 @@ export default function ObjectHeaterCard(props: Props) {
}else { }else {
return ( return (
<React.Fragment> <React.Fragment>
<CardContent style={{ paddingLeft: 30, paddingRight: 30 }}> <CardContent style={{ padding: 20 }}>
<Grid <Grid
container container
direction="row" direction="row"

View file

@ -1117,6 +1117,7 @@ export default function Bins(props: Props) {
<MenuItem <MenuItem
onClick={() => { onClick={() => {
setOpenQrGenerator(true); setOpenQrGenerator(true);
setBinMenuAnchorEl(null)
}}> }}>
<Typography>Generate QR Codes</Typography> <Typography>Generate QR Codes</Typography>
</MenuItem> </MenuItem>

View file

@ -39,6 +39,7 @@ import { useGlobalState } from "providers";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { downloadFile } from "utils/download"; import { downloadFile } from "utils/download";
import { or } from "utils/types"; import { or } from "utils/types";
import { cloneDeep } from "lodash";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
const themeType = useThemeType() const themeType = useThemeType()
@ -463,6 +464,22 @@ export default function Firmware() {
columns={columns(width === "xs" || width === "sm")} columns={columns(width === "xs" || width === "sm")}
setPage={(page)=>{setTablePage(page)}} setPage={(page)=>{setTablePage(page)}}
handleRowsPerPageChange={(e)=>{setPageSize(e.target.value)}} handleRowsPerPageChange={(e)=>{setPageSize(e.target.value)}}
loadMore={() => {
let current = cloneDeep(tableData)
firmwareAPI.listFirmware(pageSize, current.length, "desc", "version", searchText)
.then((response) => {
let newRows: FirmwareModel[] = or(response.data.firmware, []).map(
(firmwareData: any) => {
return FirmwareModel.create(pond.Firmware.fromObject(firmwareData));
}
);
setTableData(current.concat(newRows))
})
.catch(() => {
//failed to load
error("Failed to load more firmware versions")
});
}}
/> />
</Box> </Box>
<UploadFirmware <UploadFirmware

View file

@ -14,6 +14,7 @@ import { appendToUrl } from "navigation/Router";
import { getDeviceStateHelper } from "pbHelpers/DeviceState"; import { getDeviceStateHelper } from "pbHelpers/DeviceState";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { blue, green } from "@mui/material/colors"; import { blue, green } from "@mui/material/colors";
import { cloneDeep } from "lodash";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
return ({ return ({
@ -241,6 +242,36 @@ export default function GroupPage() {
onRowClick={toDevice} onRowClick={toDevice}
setSearchText={setSearch} setSearchText={setSearch}
isLoading={loadingDevices} isLoading={loadingDevices}
loadMore={()=>{
let current = cloneDeep(devices)
setLoadingDevices(true)
deviceAPI.list(
limit,
current.length,
order,
orderBy,
search,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
getKeys(),
getTypes(),
undefined,
undefined,
as
).then(resp => {
let newDevices: Device[] = [];
resp.data.devices.forEach(device => {
newDevices.push(Device.create(device))
})
setDevices(current.concat(newDevices))
}).finally(() => {
setLoadingDevices(false)
})
}}
/> />
</PageContainer> </PageContainer>
) )

View file

@ -13,6 +13,7 @@ import { or } from "utils/types";
import PageContainer from "./PageContainer"; import PageContainer from "./PageContainer";
import GroupSettings from "group/GroupSettings"; import GroupSettings from "group/GroupSettings";
import { green } from "@mui/material/colors"; import { green } from "@mui/material/colors";
import { cloneDeep } from "lodash";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
const isMobile = useMobile() const isMobile = useMobile()
@ -163,6 +164,15 @@ export default function GroupsPage() {
isLoading={loading} isLoading={loading}
setSearchText={setSearch} setSearchText={setSearch}
onRowClick={handleRowClick} onRowClick={handleRowClick}
loadMore={()=>{
let current = cloneDeep(groups)
setLoading(true)
groupAPI.listGroups(limit, current.length, order, orderBy, search).then(resp => {
setGroups(or(current.concat(resp.data.groups), current))
}).finally(() => {
setLoading(false)
})
}}
/> />
<GroupSettings <GroupSettings
// initialGroup={selectedGroup} // initialGroup={selectedGroup}

View file

@ -3,6 +3,7 @@ import DeviceLinkDrawer from "common/DeviceLinkDrawer";
import ResponsiveTable, { Column } from "common/ResponsiveTable"; import ResponsiveTable, { Column } from "common/ResponsiveTable";
//import { getTableIcons } from "common/ResponsiveTable"; //import { getTableIcons } from "common/ResponsiveTable";
import { useMobile } from "hooks"; import { useMobile } from "hooks";
import { cloneDeep } from "lodash";
//import MaterialTable from "material-table"; //import MaterialTable from "material-table";
import { Device } from "models"; import { Device } from "models";
import { ObjectHeater } from "models/ObjectHeater"; import { ObjectHeater } from "models/ObjectHeater";
@ -109,17 +110,19 @@ export default function Heaters() {
setPage={(page)=>{setTablePage(page)}} setPage={(page)=>{setTablePage(page)}}
handleRowsPerPageChange={handleChange} handleRowsPerPageChange={handleChange}
onRowClick={goToHeater} onRowClick={goToHeater}
/> loadMore={()=>{
) let current = cloneDeep(heaters)
}; objectHeaterAPI
.listObjectHeatersPageData(pageSize, current.length, undefined, undefined, searchText, as)
const mobileList = () => { .then(resp => {
return ( setHeaters(heaters.concat(resp.data.heaterData));
<List> });
{heaters.map(h => ( }}
<ObjectHeaterCard renderMobile={(row)=>{
key={h.heater?.key} return (
heaterData={h} <ObjectHeaterCard
key={row.heater?.key}
heaterData={row}
linkDevice={(heater, linkedDevices) => { linkDevice={(heater, linkedDevices) => {
setHeaterToLink(heater); setHeaterToLink(heater);
setOpenLinkDrawer(true); setOpenLinkDrawer(true);
@ -131,9 +134,10 @@ export default function Heaters() {
setDevices(d); setDevices(d);
}} }}
/> />
))} )
</List> }}
); />
)
}; };
return ( return (
@ -180,7 +184,7 @@ export default function Heaters() {
}} }}
pulse={heaters.length < 1} pulse={heaters.length < 1}
/> />
{isMobile ? mobileList() : desktopTable()} {desktopTable()}
</PageContainer> </PageContainer>
); );
} }

View file

@ -14,6 +14,7 @@ import ResponsiveTable, { Column } from "common/ResponsiveTable";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { or } from "utils"; import { or } from "utils";
import { useGlobalState } from "providers"; import { useGlobalState } from "providers";
import { cloneDeep } from "lodash";
interface MineRow { interface MineRow {
mine: pond.Mine; mine: pond.Mine;
@ -110,6 +111,24 @@ export default function Mines() {
hideKeys hideKeys
isLoading={isLoading} isLoading={isLoading}
onRowClick={onRowClick} onRowClick={onRowClick}
loadMore={()=>{
let current = cloneDeep(mineTableData)
setIsLoading(true);
mineAPI
.listMines(pageSize, current.length, "desc", undefined, searchValue, false, as)
.then(resp => {
let newMines: MineRow[] = [];
resp.data.mines.forEach(mine => {
newMines.push({
mine: mine
});
});
setMineTableData(current.concat(newMines));
})
.finally(() => {
setIsLoading(false);
});
}}
/> />
<AddMine open={mineDialog} closeCallback={closeCallback} refreshCallback={load} /> <AddMine open={mineDialog} closeCallback={closeCallback} refreshCallback={load} />
</PageContainer> </PageContainer>

View file

@ -1,4 +1,4 @@
import { Avatar, Box, Card, Grid2 as Grid, Switch, Tab, Tabs, Tooltip, Typography } from "@mui/material"; import { Avatar, Box, Button, Card, Grid2 as Grid, Switch, Tab, Tabs, Tooltip, Typography } from "@mui/material";
import { Group } from "@mui/icons-material"; import { Group } from "@mui/icons-material";
//import MaterialTable from "material-table"; //import MaterialTable from "material-table";
import { Site, User } from "models"; import { Site, User } from "models";
@ -12,6 +12,7 @@ import { useMobile } from "hooks";
import JobsiteIcon from "products/Construction/JobSiteIcon"; import JobsiteIcon from "products/Construction/JobSiteIcon";
import ResponsiveTable, { Column } from "common/ResponsiveTable"; import ResponsiveTable, { Column } from "common/ResponsiveTable";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { cloneDeep } from "lodash";
interface SiteWithData { interface SiteWithData {
site: Site; site: Site;
@ -46,7 +47,7 @@ export default function Sites() {
.listSitesPageData(pageSize, tablePage * pageSize, "asc", "siteName", undefined, as) .listSitesPageData(pageSize, tablePage * pageSize, "asc", "siteName", undefined, as)
.then(resp => { .then(resp => {
setTotal(resp.data.total); setTotal(resp.data.total);
buildTableData(resp.data.sites); setSites(buildTableData(resp.data.sites));
}) })
.finally(() => { .finally(() => {
setLoading(false); setLoading(false);
@ -71,7 +72,7 @@ export default function Sites() {
data.push(newTableData); data.push(newTableData);
} }
}); });
setSites([...data]); return data;
}; };
useEffect(() => { useEffect(() => {
@ -186,6 +187,19 @@ export default function Sites() {
rows={sites} rows={sites}
handleRowsPerPageChange={handleTablePage} handleRowsPerPageChange={handleTablePage}
onRowClick={goToSite} 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);
});
}}
/> />
) )
}; };
@ -208,6 +222,20 @@ export default function Sites() {
setCurrentTab(newValue); 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 = () => { const mobileView = () => {
return ( return (
<React.Fragment> <React.Fragment>
@ -297,6 +325,9 @@ export default function Sites() {
return siteCards; return siteCards;
})} })}
</TabPanelMine> </TabPanelMine>
{sites.length < total &&
<Button onClick={loadMore} variant="contained" color="primary" fullWidth>Load More</Button>
}
</React.Fragment> </React.Fragment>
); );
}; };