added a column for checkboxes to be able to select rows which can be controled by the parent

This commit is contained in:
csawatzky 2025-03-18 15:50:42 -06:00
parent b7c2354b83
commit 6221313baf
4 changed files with 281 additions and 213 deletions

View file

@ -369,7 +369,6 @@ export default function BinStorageConditions(props: Props) {
//this will display at all times //this will display at all times
const tempDisplay = () => { const tempDisplay = () => {
console.log(tempTargets)
let sliderEdge = 40; let sliderEdge = 40;
let mark: Mark[] = []; let mark: Mark[] = [];

View file

@ -88,6 +88,8 @@ interface Props<T> {
hideKeys?: boolean; hideKeys?: boolean;
onRowClick?: (row: T) => void; onRowClick?: (row: T) => void;
actions?: string | JSX.Element; actions?: string | JSX.Element;
rowSelect?: (row: T, index: number, checked: boolean) => void; //row select function that passes the the row back up, when this function is passed in render a column of checkboxes
selectedRows?: number[]; //which rows are selected will need to be controlled by the parent in order to keep track between page changes
} }
export default function ResponsiveTable<T extends Object>(props: Props<T>) { export default function ResponsiveTable<T extends Object>(props: Props<T>) {
@ -110,7 +112,9 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
renderMobile, renderMobile,
hideKeys, hideKeys,
onRowClick, onRowClick,
actions actions,
rowSelect,
selectedRows
} = props } = props
const classes = useStyles(); const classes = useStyles();
const columns = typeof(props.columns) === "function" ? props.columns() : props.columns const columns = typeof(props.columns) === "function" ? props.columns() : props.columns
@ -399,6 +403,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow sx={ hideKeys ? { display: "none" } : undefined } > <TableRow sx={ hideKeys ? { display: "none" } : undefined } >
{ rowSelect && <TableCell></TableCell> }
{ renderGutter && <TableCell></TableCell>} { renderGutter && <TableCell></TableCell>}
{ columns ? { columns ?
columns.map((column, index) => { columns.map((column, index) => {
@ -435,6 +440,16 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
return ( return (
<React.Fragment key={"row-"+index}> <React.Fragment key={"row-"+index}>
<TableRow onClick={() => onRowClick&&onRowClick(row)} className={onRowClick ? classes.rowHover : undefined}> <TableRow onClick={() => onRowClick&&onRowClick(row)} className={onRowClick ? classes.rowHover : undefined}>
{rowSelect &&
<TableCell>
<Checkbox
checked={selectedRows ? selectedRows.includes(index) : false}
onClick={(event) => {event.stopPropagation()}}
onChange={(e, checked) => {
rowSelect(row, index, checked)
}}/>
</TableCell>
}
{renderGutter && {renderGutter &&
<TableCell width={1}> <TableCell width={1}>
<IconButton sx={{ <IconButton sx={{

View file

@ -1,10 +1,11 @@
import { Column } from "common/ResponsiveTable";
import { Option } from "common/SearchSelect"; import { Option } from "common/SearchSelect";
import GrainDescriber from "grain/GrainDescriber"; import GrainDescriber from "grain/GrainDescriber";
import { Column } from "material-table"; //import { Column } from "material-table";
import { Bin, BinYard, Field } from "models"; import { Bin, BinYard, Field } from "models";
import { Gate } from "models/Gate"; //import { Gate } from "models/Gate";
import { GrainBag } from "models/GrainBag"; import { GrainBag } from "models/GrainBag";
import { ObjectHeater } from "models/ObjectHeater"; //import { ObjectHeater } from "models/ObjectHeater";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { getDistanceUnit, getTemperatureUnit } from "utils"; import { getDistanceUnit, getTemperatureUnit } from "utils";
@ -52,12 +53,14 @@ export const ObjectExtensions: Map<pond.ObjectType, ObjectExtension> = new Map([
tableColumns: [ tableColumns: [
{ {
title: "Name", title: "Name",
cellStyle: {padding: "16px"},
render: row => { render: row => {
return row.settings.name; return (<div>{row.settings.name}</div>);
} }
}, },
{ {
title: "Grain", title: "Grain",
cellStyle: {padding: "16px"},
render: row => { render: row => {
let grain = row.settings.inventory?.grainType; let grain = row.settings.inventory?.grainType;
if (grain) { if (grain) {
@ -67,6 +70,7 @@ export const ObjectExtensions: Map<pond.ObjectType, ObjectExtension> = new Map([
}, },
{ {
title: "Bin Height", title: "Bin Height",
cellStyle: {padding: "16px"},
render: row => { render: row => {
let d = row.settings.specs?.heightCm; let d = row.settings.specs?.heightCm;
if (d) { if (d) {
@ -80,6 +84,7 @@ export const ObjectExtensions: Map<pond.ObjectType, ObjectExtension> = new Map([
}, },
{ {
title: "Bin Diameter", title: "Bin Diameter",
cellStyle: {padding: "16px"},
render: row => { render: row => {
let d = row.settings.specs?.diameterCm; let d = row.settings.specs?.diameterCm;
if (d) { if (d) {
@ -93,30 +98,35 @@ export const ObjectExtensions: Map<pond.ObjectType, ObjectExtension> = new Map([
}, },
{ {
title: "Custom Grain Name", title: "Custom Grain Name",
cellStyle: {padding: "16px"},
render: row => { render: row => {
return row.settings.inventory?.customTypeName; return row.settings.inventory?.customTypeName;
} }
}, },
{ {
title: "Grain Variant", title: "Grain Variant",
cellStyle: {padding: "16px"},
render: row => { render: row => {
return row.settings.inventory?.grainSubtype; return row.settings.inventory?.grainSubtype;
} }
}, },
{ {
title: "Grain Bushels", title: "Grain Bushels",
cellStyle: {padding: "16px"},
render: row => { render: row => {
return row.settings.inventory?.grainBushels; return row.settings.inventory?.grainBushels;
} }
}, },
{ {
title: "Grain Capacity", title: "Grain Capacity",
cellStyle: {padding: "16px"},
render: row => { render: row => {
return row.settings.specs?.bushelCapacity; return row.settings.specs?.bushelCapacity;
} }
}, },
{ {
title: "High Temp Warning", title: "High Temp Warning",
cellStyle: {padding: "16px"},
render: row => { render: row => {
let t = row.settings.highTemp; let t = row.settings.highTemp;
if (t) { if (t) {
@ -130,6 +140,7 @@ export const ObjectExtensions: Map<pond.ObjectType, ObjectExtension> = new Map([
}, },
{ {
title: "Low Temp Warning", title: "Low Temp Warning",
cellStyle: {padding: "16px"},
render: row => { render: row => {
let t = row.settings.lowTemp; let t = row.settings.lowTemp;
if (t) { if (t) {
@ -166,7 +177,7 @@ export const ObjectExtensions: Map<pond.ObjectType, ObjectExtension> = new Map([
{ {
title: "Name", title: "Name",
render: row => { render: row => {
return row.settings.name; return (<div>{row.settings.name}</div>)
} }
}, },
{ {
@ -212,7 +223,7 @@ export const ObjectExtensions: Map<pond.ObjectType, ObjectExtension> = new Map([
{ {
title: "Name", title: "Name",
render: row => { render: row => {
return row.settings.fieldName; return (<div>{row.settings.fieldName}</div>)
} }
}, },
{ {
@ -276,108 +287,108 @@ export const ObjectExtensions: Map<pond.ObjectType, ObjectExtension> = new Map([
tableSort: new Map() tableSort: new Map()
} }
], ],
[ // [
pond.ObjectType.OBJECT_TYPE_GATE, // pond.ObjectType.OBJECT_TYPE_GATE,
{ // {
name: "Gate", // name: "Gate",
inventoryGroup: "", // inventoryGroup: "",
isTransactionObject: false, // isTransactionObject: false,
tableColumns: [ // tableColumns: [
{ // {
title: "Name", // title: "Name",
render: row => { // render: row => {
if (row.name) { // if (row.name) {
return row.name.toString(); // return row.name.toString();
} // }
} // }
}, // },
{ // {
title: "PCA Type", // title: "PCA Type",
render: row => { // render: row => {
return row.settings.pcaType; // return row.settings.pcaType;
} // }
}, // },
{ // {
title: "Hourly APU Cost", // title: "Hourly APU Cost",
render: row => { // render: row => {
if (row.settings.hourlyApuCost) { // if (row.settings.hourlyApuCost) {
return "$" + row.settings.hourlyApuCost.toFixed(2); // return "$" + row.settings.hourlyApuCost.toFixed(2);
} // }
} // }
}, // },
{ // {
title: "Hourly PCA Cost", // title: "Hourly PCA Cost",
render: row => { // render: row => {
if (row.settings.hourlyPcaCost) { // if (row.settings.hourlyPcaCost) {
return "$" + row.settings.hourlyPcaCost.toFixed(2); // return "$" + row.settings.hourlyPcaCost.toFixed(2);
} // }
} // }
}, // },
{ // {
title: "Upper Flow", // title: "Upper Flow",
render: row => { // render: row => {
return row.settings.upperFlow; // return row.settings.upperFlow;
} // }
}, // },
{ // {
title: "Lower Flow", // title: "Lower Flow",
render: row => { // render: row => {
return row.settings.lowerFlow; // return row.settings.lowerFlow;
} // }
}, // },
{ // {
title: "Duct Name", // title: "Duct Name",
render: row => { // render: row => {
return row.settings.ductName; // return row.settings.ductName;
} // }
}, // },
{ // {
title: "Duct Length", // title: "Duct Length",
render: row => { // render: row => {
return row.settings.ductLength + " m"; // return row.settings.ductLength + " m";
} // }
}, // },
{ // {
title: "Duct Diameter", // title: "Duct Diameter",
render: row => { // render: row => {
return row.settings.ductDiameter + " mm"; // return row.settings.ductDiameter + " mm";
} // }
}, // },
{ // {
title: "Friction Factor", // title: "Friction Factor",
render: row => { // render: row => {
return row.settings.frictionFactor; // return row.settings.frictionFactor;
} // }
}, // },
{ // {
title: "Thermal Conductivity", // title: "Thermal Conductivity",
render: row => { // render: row => {
return row.settings.thermalConductivity + " W/mK"; // return row.settings.thermalConductivity + " W/mK";
} // }
}, // },
{ // {
title: "Thermal Resistance", // title: "Thermal Resistance",
render: row => { // render: row => {
return row.settings.thermalResistance + " K/W"; // return row.settings.thermalResistance + " K/W";
} // }
} // }
] as Column<Gate>[], // ] as Column<Gate>[],
tableSort: new Map([ // tableSort: new Map([
["Name", { order: "name", numerical: false }], // ["Name", { order: "name", numerical: false }],
["PCA Type", { order: "pcaType", numerical: false }], // ["PCA Type", { order: "pcaType", numerical: false }],
["Hourly APU Cost", { order: "hourlyApuCost", numerical: true }], // ["Hourly APU Cost", { order: "hourlyApuCost", numerical: true }],
["Hourly PCA Cost", { order: "hourlyPcaCost", numerical: true }], // ["Hourly PCA Cost", { order: "hourlyPcaCost", numerical: true }],
["Upper Flow", { order: "upperFlow", numerical: true }], // ["Upper Flow", { order: "upperFlow", numerical: true }],
["Lower Flow", { order: "lowerFlow", numerical: true }], // ["Lower Flow", { order: "lowerFlow", numerical: true }],
["Duct Name", { order: "ductName", numerical: false }], // ["Duct Name", { order: "ductName", numerical: false }],
["Duct Length", { order: "ductLength", numerical: true }], // ["Duct Length", { order: "ductLength", numerical: true }],
["Duct Diameter", { order: "ductDiameter", numerical: true }], // ["Duct Diameter", { order: "ductDiameter", numerical: true }],
["Friction Factor", { order: "frictionFactor", numerical: true }], // ["Friction Factor", { order: "frictionFactor", numerical: true }],
["Thermal Conductivity", { order: "thermalConductivity", numerical: true }], // ["Thermal Conductivity", { order: "thermalConductivity", numerical: true }],
["Thermal Resistance", { order: "thermalResistance", numerical: true }] // ["Thermal Resistance", { order: "thermalResistance", numerical: true }]
]) // ])
} // }
], // ],
[ [
pond.ObjectType.OBJECT_TYPE_GRAIN_BAG, pond.ObjectType.OBJECT_TYPE_GRAIN_BAG,
{ {
@ -389,7 +400,7 @@ export const ObjectExtensions: Map<pond.ObjectType, ObjectExtension> = new Map([
title: "Name", title: "Name",
render: row => { render: row => {
if (row.title) { if (row.title) {
return row.title.toString(); return (<div>{row.title.toString()}</div>);
} }
} }
}, },
@ -496,74 +507,74 @@ export const ObjectExtensions: Map<pond.ObjectType, ObjectExtension> = new Map([
tableSort: new Map() tableSort: new Map()
} }
], ],
[ // [
pond.ObjectType.OBJECT_TYPE_HEATER, // pond.ObjectType.OBJECT_TYPE_HEATER,
{ // {
name: "Heater", // name: "Heater",
inventoryGroup: "", // inventoryGroup: "",
isTransactionObject: false, // isTransactionObject: false,
tableColumns: [ // tableColumns: [
{ // {
title: "Name", // title: "Name",
render: row => { // render: row => {
if (row.name) { // if (row.name) {
return row.name.toString(); // return row.name.toString();
} // }
} // }
}, // },
{ // {
title: "Make", // title: "Make",
render: row => { // render: row => {
return row.settings.make; // return row.settings.make;
} // }
}, // },
{ // {
title: "Model", // title: "Model",
render: row => { // render: row => {
return row.settings.model; // return row.settings.model;
} // }
}, // },
{ // {
title: "Fuel Type", // title: "Fuel Type",
render: row => { // render: row => {
let fuelMap = new Map<pond.FuelType, string>([ // let fuelMap = new Map<pond.FuelType, string>([
[pond.FuelType.FUEL_TYPE_DIESEL, "Diesel"], // [pond.FuelType.FUEL_TYPE_DIESEL, "Diesel"],
[pond.FuelType.FUEL_TYPE_GASOLINE, "Gasoline"], // [pond.FuelType.FUEL_TYPE_GASOLINE, "Gasoline"],
[pond.FuelType.FUEL_TYPE_PROPANE, "Propane"] // [pond.FuelType.FUEL_TYPE_PROPANE, "Propane"]
]); // ]);
return fuelMap.get(row.settings.fuelType); // return fuelMap.get(row.settings.fuelType);
} // }
}, // },
{ // {
title: "Tank Size", // title: "Tank Size",
render: row => { // render: row => {
return row.settings.tankSize + " G"; // return row.settings.tankSize + " G";
} // }
}, // },
{ // {
title: "Fuel Consumption", // title: "Fuel Consumption",
render: row => { // render: row => {
return row.settings.fuelConsumption + " G/hr"; // return row.settings.fuelConsumption + " G/hr";
} // }
}, // },
{ // {
title: "Air Circulation", // title: "Air Circulation",
render: row => { // render: row => {
return row.settings.airCirculation + " CFM"; // return row.settings.airCirculation + " CFM";
} // }
} // }
] as Column<ObjectHeater>[], // ] as Column<ObjectHeater>[],
tableSort: new Map([ // tableSort: new Map([
["Name", { order: "name", numerical: false }], // ["Name", { order: "name", numerical: false }],
["Make", { order: "make", numerical: false }], // ["Make", { order: "make", numerical: false }],
["Model", { order: "model", numerical: false }], // ["Model", { order: "model", numerical: false }],
["Fuel Type", { order: "fuelType", numerical: false }], // ["Fuel Type", { order: "fuelType", numerical: false }],
["Tank Size", { order: "tankSize", numerical: true }], // ["Tank Size", { order: "tankSize", numerical: true }],
["Fuel Consumption", { order: "fuelConsumption", numerical: true }], // ["Fuel Consumption", { order: "fuelConsumption", numerical: true }],
["Air Circulation", { order: "airCirculation", numerical: true }] // ["Air Circulation", { order: "airCirculation", numerical: true }]
]) // ])
} // }
], // ],
[ [
pond.ObjectType.OBJECT_TYPE_HOMEMARKER, pond.ObjectType.OBJECT_TYPE_HOMEMARKER,
{ {

View file

@ -24,6 +24,7 @@ import TeamSearch from "teams/TeamSearch";
import BulkBinSettings from "./bulkEditForms/bulkBinSettings"; import BulkBinSettings from "./bulkEditForms/bulkBinSettings";
import BulkGrainBagSettings from "./bulkEditForms/bulkGrainBagSettings"; import BulkGrainBagSettings from "./bulkEditForms/bulkGrainBagSettings";
import ResponsiveTable from "common/ResponsiveTable"; import ResponsiveTable from "common/ResponsiveTable";
import { cloneDeep } from "lodash";
//import BulkGateSettings from "./bulkEditForms/bulkGateSettings"; //import BulkGateSettings from "./bulkEditForms/bulkGateSettings";
interface customButton { interface customButton {
@ -49,13 +50,15 @@ export default function ObjectTable(props: Props) {
const [pageSize, setPageSize] = useState(10); //offset would be determined by the pageSize * tablePage, limit is just pageSize const [pageSize, setPageSize] = useState(10); //offset would be determined by the pageSize * tablePage, limit is just pageSize
const [tablePage, setTablePage] = useState(0); const [tablePage, setTablePage] = useState(0);
const [tableData, setTableData] = useState<any[]>([]); const [tableData, setTableData] = useState<any[]>([]);
const [loadedData, setLoadedData] = useState<any[]>([]);
const [selectedObjects, setSelectedObjects] = useState<any[]>([]); const [selectedObjects, setSelectedObjects] = useState<any[]>([]);
const [currentDirection, setCurrentDirection] = useState<"asc" | "desc">("asc"); const [currentDirection, setCurrentDirection] = useState<"asc" | "desc">("asc");
const [currentSearchText, setCurrentSearchText] = useState<string | undefined>(); const [currentSearchText, setCurrentSearchText] = useState<string>("");
const [currentOrder, setCurrentOrder] = useState<string | undefined>(); const [currentOrder, setCurrentOrder] = useState<string | undefined>();
const [isNumerical, setIsNumerical] = useState<boolean | undefined>(); const [isNumerical, setIsNumerical] = useState<boolean | undefined>();
const [{ user, as }] = useGlobalState(); const [{ user, as }] = useGlobalState();
const [selectedUser, setSelectedUser] = useState<string>(as ?? user.id()); const [selectedUser, setSelectedUser] = useState<string>(as ?? user.id());
const [selectedPageRows, setSelectedPageRows] = useState<Map<number, number[]>>(new Map())//key is the page value is an array of row indexes for that page
const [tableLoading, setTableLoading] = useState(false); const [tableLoading, setTableLoading] = useState(false);
//the api's to load all the objects available to look at in this table //the api's to load all the objects available to look at in this table
const binAPI = useBinAPI(); const binAPI = useBinAPI();
@ -94,7 +97,8 @@ export default function ObjectTable(props: Props) {
isNumerical isNumerical
) )
.then(resp => { .then(resp => {
setTableData(resp.data.bins.map(b => Bin.create(b))); setLoadedData(resp.data.bins.map(b => Bin.create(b)))
//setTableData(resp.data.bins.map(b => Bin.create(b)));
setObjectTotal(resp.data.total); setObjectTotal(resp.data.total);
setTableLoading(false); setTableLoading(false);
}); });
@ -211,13 +215,25 @@ export default function ObjectTable(props: Props) {
]); ]);
useEffect(() => { useEffect(() => {
if (preLoadedData) { if (preLoadedData && currentSearchText === "") {
setTableData(preLoadedData); setLoadedData(preLoadedData);
setObjectTotal(preLoadedData.length); setObjectTotal(preLoadedData.length);
} else { } else {
load(); load();
} }
}, [load, preLoadedData]); }, [load, preLoadedData, currentSearchText]);
useEffect(()=>{
let start = pageSize * (tablePage)
let end = pageSize * (tablePage + 1)
let currentData: any[] = []
loadedData.forEach((object,i) => {
if (i >= start && i < end ) {
currentData.push(object)
}
})
setTableData(currentData)
},[loadedData, pageSize, tablePage])
const bulkEditForm = (type: pond.ObjectType) => { const bulkEditForm = (type: pond.ObjectType) => {
switch (type) { switch (type) {
@ -248,34 +264,34 @@ export default function ObjectTable(props: Props) {
} }
}; };
const actionToolbar = () => { // const actionToolbar = () => {
return ( // return (
<Box width="100%" padding={2}> // <Box width="100%" padding={2}>
<SearchBar // <SearchBar
value={currentSearchText ?? ""} // value={currentSearchText ?? ""}
onChange={val => { // onChange={val => {
setCurrentSearchText(val); // setCurrentSearchText(val);
}} // }}
/> // />
{selectedObjects.length > 0 && <Box paddingTop={2}>{bulkEditForm(currentType)}</Box>} // {selectedObjects.length > 0 && <Box paddingTop={2}>{bulkEditForm(currentType)}</Box>}
<Grid container direction="row" spacing={2} style={{ marginTop: 2 }}> // <Grid container direction="row" spacing={2} style={{ marginTop: 2 }}>
{customButtons && // {customButtons &&
selectedObjects.length > 0 && // selectedObjects.length > 0 &&
customButtons.map((button, i) => ( // customButtons.map((button, i) => (
<Grid size={{ xs: 3 }} key={i}> // <Grid size={{ xs: 3 }} key={i}>
<Button // <Button
style={{ width: "100%" }} // style={{ width: "100%" }}
variant="contained" // variant="contained"
color="primary" // color="primary"
onClick={() => button.function(selectedObjects)}> // onClick={() => button.function(selectedObjects)}>
{button.label} // {button.label}
</Button> // </Button>
</Grid> // </Grid>
))} // ))}
</Grid> // </Grid>
</Box> // </Box>
); // );
}; // };
const handleRowsPerPageChange = (event: any) => { const handleRowsPerPageChange = (event: any) => {
const newRowsPerPage = parseInt(event.target.value, 10); // Get selected value const newRowsPerPage = parseInt(event.target.value, 10); // Get selected value
@ -299,7 +315,7 @@ export default function ObjectTable(props: Props) {
if (op?.value) { if (op?.value) {
setCurrentType(op.value); setCurrentType(op.value);
setTablePage(0); setTablePage(0);
setCurrentSearchText(undefined); setCurrentSearchText("");
} }
}} }}
/> />
@ -319,11 +335,38 @@ export default function ObjectTable(props: Props) {
<ResponsiveTable <ResponsiveTable
rows={tableData} rows={tableData}
columns={ObjectDescriber(currentType).tableColumns}
onRowClick={rowClickFunction}
total={objectTotal} total={objectTotal}
pageSize={pageSize} pageSize={pageSize}
page={tablePage} page={tablePage}
setPage={setTablePage} setPage={setTablePage}
handleRowsPerPageChange={handleRowsPerPageChange} handleRowsPerPageChange={handleRowsPerPageChange}
setSearchText={setCurrentSearchText}
rowSelect={(row, index, checked) => {
//when a row is selected add it to the map for the page
// console.log(row)
// console.log(checked)
// console.log(index)
let pageMap = cloneDeep(selectedPageRows)
let pageSelect = pageMap.get(tablePage)
if(checked){ //add the page/index to the map
if(pageSelect){
pageSelect.push(index)
}else{
pageMap.set(tablePage, [index])
}
//handle the row data to add it to a list of objects that are selected
}else{ //remove the page/index from the map
if(pageSelect){
pageSelect.splice(pageSelect.indexOf(index))
}
}
console.log(pageMap)
setSelectedPageRows(pageMap)
}}
selectedRows={selectedPageRows.get(tablePage)}
/> />
{/* <MaterialTable {/* <MaterialTable
isLoading={tableLoading} isLoading={tableLoading}