frontend/src/field/FieldList.tsx
2025-08-29 12:36:45 -06:00

294 lines
No EOL
9.2 KiB
TypeScript

import {
Accordion,
AccordionDetails,
AccordionSummary,
Box,
Button,
Grid2,
IconButton,
Paper,
Stack,
Tab,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Tabs,
Typography
} from "@mui/material";
import React, { useCallback, useEffect, useState } from "react";
import { useMobile, useSnackbar, useUserAPI } from "hooks";
import { useGlobalState, useFieldAPI, useJohnDeereProxyAPI, useCNHiProxyAPI } from "providers";
//import HarvestTable from "harvestPlan/HarvestTable";
import { Field, fieldScope, teamScope } from "models";
import GrainDescriber from "grain/GrainDescriber";
import { pond } from "protobuf-ts/pond";
import HarvestSettings from "harvestPlan/HarvestSettings";
import ResponsiveTable, { Column } from "common/ResponsiveTable";
import FieldMinimap from "./Fieldminimap";
import FieldActions from "./FieldActions";
import FieldSettings from "./FieldSettings";
import { ArrowForward, ExpandMore, Settings } from "@mui/icons-material";
import { cloneDeep } from "lodash";
import ShareAllFields from "./ShareAllFields";
import EventBlocker from "common/EventBlocker";
import { useNavigate } from "react-router-dom";
interface TabPanelProps {
children?: React.ReactNode;
index: any;
value: any;
}
export default function FieldList() {
const [{ as }] = useGlobalState();
const isMobile = useMobile();
const fieldAPI = useFieldAPI();
const jdAPI = useJohnDeereProxyAPI();
const cnhAPI = useCNHiProxyAPI();
const { openSnack } = useSnackbar();
const [tabValue, setTabValue] = React.useState(0);
const [openHarvestSettings, setOpenHarvestSettings] = useState(false);
// const [fieldForPlan, setFieldForPlan] = useState(Field.create());
const [fields, setFields] = useState<Field[]>([]);
const [total, setTotal] = useState(0)
const [rowsPerPage, setRowsPerPage] = useState(5)
const [page, setPage] = useState(0)
const [selectedField, setSelectedField] = useState<Field>()
const [openFieldSettings, setOpenFieldSettings] = useState(false)
const [shareOpen, setShareOpen] = useState(false)
const navigate = useNavigate();
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
setTabValue(newValue);
setPage(0)
};
const loadAdaptiveFields = useCallback(() => {
fieldAPI
.listFields(rowsPerPage, page*rowsPerPage, "asc", "fieldName", undefined, as)
.then(resp => {
setFields(resp.data.fields.map(f => Field.create(f)));
setTotal(resp.data.total)
})
.catch(err => {
openSnack("Failed to load Fields");
});
}, [fieldAPI, as, openSnack, page, rowsPerPage]);
const loadJDFields = useCallback(() => {
jdAPI.listFields(rowsPerPage, page*rowsPerPage, as)
.then(resp => {
resp.data.fields ? setFields(resp.data.fields.map(f => Field.create(f))) : setFields([])
}).catch(err => {
openSnack("Failed to load John Deere Fields");
})
},[jdAPI, as, openSnack, page, rowsPerPage])
const loadCNHFields = useCallback(() => {
cnhAPI.listFields(rowsPerPage, page*rowsPerPage, as)
.then(resp => {
resp.data.fields ? setFields(resp.data.fields.map(f => Field.create(f))) : setFields([])
}).catch(err => {
openSnack("Failed to load John Deere Fields");
})
},[cnhAPI, as, openSnack, page, rowsPerPage])
useEffect(() => {
//based on the current tab load the correct fields
switch(tabValue){
case 0:
loadAdaptiveFields();
break;
case 1:
loadJDFields();
break;
case 2:
loadCNHFields();
break;
}
}, [loadAdaptiveFields, loadJDFields, loadCNHFields, tabValue]);
const calcTotalAcres = (fields: Field[]) => {
let totalAcres = 0;
fields.forEach(field => {
totalAcres += field.acres();
});
totalAcres = Math.round(totalAcres);
return totalAcres;
};
const goTo = (field: Field) => {
let path = field.key();
navigate(path, { state: {field: field, permissions: field.permissions} });
}
const fieldActions = (field: Field) => {
return (
<EventBlocker>
<IconButton
onClick={() => {
setSelectedField(field)
setOpenFieldSettings(true)
}}><Settings /></IconButton>
<FieldActions field={field} permissions={field.permissions} refreshCallback={()=>{}}/>
{isMobile && <IconButton onClick={() => {goTo(field)}}><ArrowForward /></IconButton>}
</EventBlocker>
)
}
const columns: Column<Field>[] = [
{
title: "View",
render: row => {
return (
<Box margin={1} height={200} width={200}>
<FieldMinimap field={row} squared borderSpacing={0.001}/>
</Box>
)
}
},
{
title: "Field Name",
render: row => {
return <Typography paddingLeft={2}>{row.name()}</Typography>
}
},
{
title: "Current Crop",
render: row => {
return <Typography paddingLeft={2}>{GrainDescriber(row.crop()).name}</Typography>
}
},
{
title: "Acres",
render: row => {
return <Typography paddingLeft={2}>{row.acres()}</Typography>
}
},
{
title: "Actions",
hidden: tabValue > 0,
render: row => fieldActions(row)
}
]
const fieldTable = (fields: Field[]) => {
return (
<ResponsiveTable<Field>
columns={columns}
rows={fields}
onRowClick={(row) => {
!isMobile && goTo(row)
}}
setPage={(newPage)=>{
setPage(newPage)
}}
handleRowsPerPageChange={(e)=>{setRowsPerPage(e.target.value)}}
page={page}
pageSize={rowsPerPage}
total={total}
resizeable
loadMore={() => {
let currentFields = cloneDeep(fields)
switch(tabValue){
case 0:
fieldAPI.listFields(rowsPerPage, currentFields.length, "asc", "fieldName", undefined, as).then(resp => {
setFields(currentFields.concat(resp.data.fields.map(f => Field.create(f))))
}).catch(err => {
openSnack("Failed to load more fields")
})
break;
case 1:
jdAPI.listFields(rowsPerPage, currentFields.length, as).then(resp => {
resp.data.fields ? setFields(currentFields.concat(resp.data.fields.map(f => Field.create(f)))) : setFields([])
}).catch(err => {
openSnack("Failed to load more fields")
})
break;
case 2:
cnhAPI.listFields(rowsPerPage, currentFields.length, as).then(resp => {
resp.data.fields ? setFields(currentFields.concat(resp.data.fields.map(f => Field.create(f)))) : setFields([])
}).catch(err => {
openSnack("Failed to load more fields")
})
break;
}
}}
renderMobile={(row, index) => {
return (
<Accordion>
<AccordionSummary expandIcon={<ExpandMore />}>
<Grid2 alignItems="center" width="100%" container direction={"row"} justifyContent="space-between" wrap="nowrap">
<Grid2>
<Typography textAlign="center">
{row.name()}
</Typography>
</Grid2>
<Grid2>
{fieldActions(row)}
</Grid2>
</Grid2>
</AccordionSummary>
<AccordionDetails>
<Grid2 alignItems="center" width="100%" container direction={"row"} justifyContent="space-between" wrap="nowrap">
<Grid2 size={6}>
<Box height={150} width="100%">
<FieldMinimap field={row} borderSpacing={0.001} squared />
</Box>
</Grid2>
<Grid2 size={6}>
<Stack spacing={2}>
<Typography textAlign="center">{row.grainName()}</Typography>
<Typography textAlign="center">{row.acres()} Acres</Typography>
</Stack>
<Button variant="contained" color="primary">Go</Button>
</Grid2>
</Grid2>
</AccordionDetails>
</Accordion>
)
}}
/>
)
}
return (
<Box>
<Button
onClick={()=>{setShareOpen(true)}}
variant="contained"
color="primary">
Share All
</Button>
<Tabs
value={tabValue}
onChange={handleChange}
TabIndicatorProps={{ style: { background: "rgba(255,255,0,255)" } }}>
<Tab label="Adaptive" />
<Tab label="John Deere" />
<Tab label="Case New Holland" />
</Tabs>
{fieldTable(fields)}
{/* <HarvestSettings
open={openHarvestSettings}
close={() => setOpenHarvestSettings(false)}
field={fieldForPlan}
/> */}
<FieldSettings
selectedField={selectedField}
removeField={() => {
//will need to re-load the fields after one is deleted
loadAdaptiveFields()
}}
open={openFieldSettings}
onClose={() => {
setOpenFieldSettings(false);
}}
/>
<ShareAllFields open={shareOpen} close={() => {setShareOpen(false)}}/>
</Box>
);
}