frontend/src/objects/ObjectTable.tsx

449 lines
14 KiB
TypeScript

import { Box, Button, Grid2 as Grid } from "@mui/material";
// import { getTableIcons } from "common/ResponsiveTable";
import SearchSelect, { Option } from "common/SearchSelect";
// import MaterialTable, { MTableToolbar } from "material-table";
// import { Bin, BinYard, Field } from "models";
import { Bin } from "models";
// import { Gate } from "models/Gate";
// import { GrainBag } from "models/GrainBag";
// import { ObjectHeater } from "models/ObjectHeater";
import ObjectDescriber, { SearchableObjects } from "objects/ObjectDescriber";
import { pond } from "protobuf-ts/pond";
import {
useBinAPI,
useBinYardAPI,
// useFieldAPI,
useGateAPI,
useGlobalState,
// useGrainBagAPI,
// useObjectHeaterAPI
} from "providers";
import { useCallback, useEffect, useState } from "react";
import TeamSearch from "teams/TeamSearch";
import BulkBinSettings from "./bulkEditForms/bulkBinSettings";
import ResponsiveTable from "common/ResponsiveTable";
import { cloneDeep } from "lodash";
//import BulkGateSettings from "./bulkEditForms/bulkGateSettings";
interface customButton {
label: string;
function: (selectedObjects: any[]) => void;
}
interface Props {
startingObject?: pond.ObjectType;
showControls?: boolean;
preLoadedData?: any[];
customButtons?: customButton[];
rowClickFunction?: (rowData: any) => void;
}
export default function ObjectTable(props: Props) {
const { startingObject, showControls, preLoadedData, customButtons, rowClickFunction } = props;
const [currentType, setCurrentType] = useState(
startingObject ?? pond.ObjectType.OBJECT_TYPE_UNKNOWN
);
const [selectedType, setSelectedType] = useState<Option | null>();
const [objectTotal, setObjectTotal] = useState(0);
const [pageSize, setPageSize] = useState(10); //offset would be determined by the pageSize * tablePage, limit is just pageSize
const [tablePage, setTablePage] = useState(0);
const [tableData, setTableData] = useState<any[]>([]);
const [loadedData, setLoadedData] = useState<any[]>([]);
//const [selectedObjects, setSelectedObjects] = useState<any[]>([]);
const [selectedObjects, setSelectedObjects] = useState<Map<string, any>>(new Map());
//const [currentDirection, setCurrentDirection] = useState<"asc" | "desc">("asc");
const [currentSearchText, setCurrentSearchText] = useState<string>("");
//const [currentOrder, setCurrentOrder] = useState<string | undefined>();
//const [isNumerical, setIsNumerical] = useState<boolean | undefined>();
const [{ user, as }] = useGlobalState();
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 objectExtension = ObjectDescriber(currentType);
const columns = objectExtension.getTableColumns ? objectExtension.getTableColumns(user) : objectExtension.tableColumns;
//the api's to load all the objects available to look at in this table
const binAPI = useBinAPI();
const binyardAPI = useBinYardAPI();
// const fieldAPI = useFieldAPI();
const gateAPI = useGateAPI();
// const grainBagAPI = useGrainBagAPI();
// const heaterAPI = useObjectHeaterAPI();
const load = useCallback(() => {
if (tableLoading) return;
/**
* switch case to use the correct api to load the object that was selected.
* In the future create a generic object api that takes the object type as a variable along with
* the rest that the backend then uses to load from the correct place
*/
//add a new case for each object that will be viewable in the table
let search;
if (currentSearchText) {
search = currentSearchText?.replace(/\s/g, "<->");
}
if (currentType !== pond.ObjectType.OBJECT_TYPE_UNKNOWN) {
setTableLoading(true);
}
switch (currentType) {
case pond.ObjectType.OBJECT_TYPE_BIN:
binAPI
.listBins(
pageSize,
pageSize * tablePage,
"asc",
"name",
search,
selectedUser,
undefined
)
.then(resp => {
//setLoadedData(resp.data.bins.map(b => Bin.create(b)))
setTableData(resp.data.bins.map(b => Bin.create(b)));
setObjectTotal(resp.data.total);
setTableLoading(false);
});
break;
// case pond.ObjectType.OBJECT_TYPE_BINYARD:
// binyardAPI
// .listBinYards(
// pageSize,
// pageSize * tablePage,
// currentDirection,
// currentOrder,
// search,
// undefined,
// selectedUser
// )
// .then(resp => {
// setTableData(resp.data.yard.map(b => BinYard.create(b)));
// setObjectTotal(resp.data.total);
// setTableLoading(false);
// });
// break;
// case pond.ObjectType.OBJECT_TYPE_FIELD:
// fieldAPI
// .listFields(
// pageSize,
// pageSize * tablePage,
// currentDirection,
// currentOrder,
// search,
// selectedUser
// )
// .then(resp => {
// setTableData(resp.data.fields.map(f => Field.create(f)));
// setObjectTotal(resp.data.total);
// setTableLoading(false);
// });
// break;
// case pond.ObjectType.OBJECT_TYPE_GATE:
// gateAPI
// .listGates(
// pageSize,
// pageSize * tablePage,
// currentDirection,
// currentOrder,
// search,
// undefined,
// undefined,
// undefined,
// isNumerical,
// selectedUser
// )
// .then(resp => {
// setTableData(resp.data.gates.map(g => Gate.create(g)));
// setObjectTotal(resp.data.total);
// setTableLoading(false);
// });
// break;
// case pond.ObjectType.OBJECT_TYPE_GRAIN_BAG:
// grainBagAPI
// .listGrainBags(
// pageSize,
// pageSize * tablePage,
// currentDirection,
// currentOrder,
// search,
// undefined,
// undefined,
// isNumerical,
// selectedUser
// )
// .then(resp => {
// setTableData(resp.data.grainBags.map(gb => GrainBag.create(gb)));
// setObjectTotal(resp.data.total);
// setTableLoading(false);
// });
// break;
// case pond.ObjectType.OBJECT_TYPE_HEATER:
// heaterAPI
// .listObjectHeaters(
// pageSize,
// pageSize * tablePage,
// currentDirection,
// currentOrder,
// search,
// undefined,
// selectedUser,
// undefined,
// undefined,
// isNumerical
// )
// .then(resp => {
// setTableData(resp.data.heaters.map(h => ObjectHeater.create(h)));
// setObjectTotal(resp.data.total);
// setTableLoading(false);
// });
// break;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
//currentDirection,
//currentOrder,
currentType,
selectedUser,
//isNumerical,
pageSize,
tablePage,
currentSearchText,
binAPI,
binyardAPI,
// fieldAPI,
gateAPI,
// grainBagAPI,
// heaterAPI
]);
useEffect(() => {
if (preLoadedData && currentSearchText === "") {
setLoadedData(preLoadedData);
setObjectTotal(preLoadedData.length);
} else {
load();
}
}, [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) => {
switch (type) {
case pond.ObjectType.OBJECT_TYPE_BIN:
return (
<BulkBinSettings
selectedBins={Array.from(selectedObjects.values()) as pond.Bin[]}
extraButtons={customButtons?.map(b => (
<Button variant="contained" onClick={() => {b.function(Array.from(selectedObjects.values()))}}>{b.label}</Button>
))}
refreshCallback={reLoad => {
//setSelectedObjects([])
setSelectedObjects(new Map())
setSelectedPageRows(new Map())
if (reLoad) {
load();
}
}}
/>
);
case pond.ObjectType.OBJECT_TYPE_GRAIN_BAG:
return (
// <BulkGrainBagSettings
// selectedBags={selectedObjects as GrainBag[]}
// refreshCallback={reLoad => {
// if (reLoad) {
// load();
// }
// }}
// />
undefined
);
default:
}
};
// const actionToolbar = () => {
// return (
// <Box width="100%" padding={2}>
// <SearchBar
// value={currentSearchText ?? ""}
// onChange={val => {
// setCurrentSearchText(val);
// }}
// />
// {selectedObjects.length > 0 && <Box paddingTop={2}>{bulkEditForm(currentType)}</Box>}
// <Grid container direction="row" spacing={2} style={{ marginTop: 2 }}>
// {customButtons &&
// selectedObjects.length > 0 &&
// customButtons.map((button, i) => (
// <Grid size={{ xs: 3 }} key={i}>
// <Button
// style={{ width: "100%" }}
// variant="contained"
// color="primary"
// onClick={() => button.function(selectedObjects)}>
// {button.label}
// </Button>
// </Grid>
// ))}
// </Grid>
// </Box>
// );
// };
const handleRowsPerPageChange = (event: any) => {
const newRowsPerPage = parseInt(event.target.value, 10); // Get selected value
setPageSize(newRowsPerPage);
setTablePage(0)
}
const rowSelected = (row: any, index: number, checked: boolean) => {
//when a row is selected add it to the map for the page
let selected = cloneDeep(selectedObjects)
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
selected.set(tablePage + ":" + index, row)
}else{ //remove the page/index from the map
if(pageSelect){
pageSelect.splice(pageSelect.indexOf(index))
}
//remove the selected object from the array
selected.delete(tablePage + ":" + index)
}
setSelectedObjects(selected)
setSelectedPageRows(pageMap)
}
return (
<Box padding={2}>
{showControls && (
<Grid container direction="row" spacing={2} style={{ padding: 10 }}>
<Grid>
<SearchSelect
style={{ width: 200 }}
label="From"
selected={selectedType}
options={SearchableObjects()}
changeSelection={op => {
//setSelectedObjects([]);
setSelectedObjects(new Map());
setSelectedType(op);
if (op?.value) {
setCurrentType(op.value);
setTablePage(0);
setCurrentSearchText("");
}
}}
/>
</Grid>
<Grid style={{ width: 300 }}>
<TeamSearch
label="User/Team"
loadUsers
setTeamCallback={user => {
setSelectedUser(user);
//dispatch({ key: "as", value: user.toString() });
}}
/>
</Grid>
</Grid>
)}
<ResponsiveTable
rows={tableData}
columns={columns}
onRowClick={rowClickFunction}
total={objectTotal}
pageSize={pageSize}
page={tablePage}
setPage={setTablePage}
handleRowsPerPageChange={handleRowsPerPageChange}
setSearchText={setCurrentSearchText}
customElement={bulkEditForm(currentType)}
rowSelect={rowSelected}
selectedRows={selectedPageRows.get(tablePage)}
/>
{/* <MaterialTable
isLoading={tableLoading}
key="objectList"
title={ObjectDescriber(currentType).name}
icons={getTableIcons()}
columns={ObjectDescriber(currentType, user).tableColumns}
data={tableData}
page={tablePage}
totalCount={objectTotal}
onRowClick={
rowClickFunction
? (_, data) => {
rowClickFunction(data);
}
: undefined
}
onChangePage={page => {
setTablePage(page);
}}
onOrderChange={(by, direction) => {
if (by !== -1) {
let colName = ObjectDescriber(currentType, user).tableColumns[by].title?.toString();
let order;
if (colName) {
order = ObjectDescriber(currentType, user).tableSort.get(colName);
setCurrentOrder(order?.order);
setCurrentDirection(direction);
setIsNumerical(order?.numerical);
}
}
}}
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) => {
setSelectedObjects(data);
}}
actions={[
{
tooltip: "Update Selected Objects",
icon: "edit",
onClick: (evt, data) => {console.log(data)}
}
]}
onChangeRowsPerPage={pageSize => {
setPageSize(pageSize);
}}
components={{
Toolbar: props => (
<React.Fragment>
<MTableToolbar {...props} />
{actionToolbar()}
</React.Fragment>
)
}}
/> */}
</Box>
);
}