finished the mobile view for the field page and some minor functionality changes

This commit is contained in:
csawatzky 2025-10-16 16:20:30 -06:00
parent 06da0aad25
commit a2971c6e44
4 changed files with 246 additions and 43 deletions

View file

@ -152,9 +152,7 @@ export default function FieldSettings(props: Props) {
const fieldInformation = () => { const fieldInformation = () => {
return ( return (
<Box> <React.Fragment>
<Box>
<DialogTitle>Field Information</DialogTitle>
<TextField <TextField
value={fieldName} value={fieldName}
margin="normal" margin="normal"
@ -258,7 +256,14 @@ export default function FieldSettings(props: Props) {
Field Colour Field Colour
<ColourPicker onChange={color => setFeatColor(color)} /> <ColourPicker onChange={color => setFeatColor(color)} />
</Box> </Box>
</Box> </React.Fragment>
);
};
return (
<ResponsiveDialog fullWidth open={props.open} onClose={onClose}>
<DialogTitle>Field Information</DialogTitle>
<DialogContent>{fieldInformation()}</DialogContent>
<DialogActions> <DialogActions>
<Grid container direction="row"> <Grid container direction="row">
<Grid> <Grid>
@ -282,13 +287,6 @@ export default function FieldSettings(props: Props) {
</Grid> </Grid>
</Grid> </Grid>
</DialogActions> </DialogActions>
</Box>
);
};
return (
<ResponsiveDialog fullWidth fullScreen={false} open={props.open} onClose={onClose}>
<DialogContent>{fieldInformation()}</DialogContent>
</ResponsiveDialog> </ResponsiveDialog>
); );
} }

View file

@ -1,13 +1,29 @@
import { Box, Typography } from "@mui/material" import { ExpandMore } from "@mui/icons-material"
import { Accordion, AccordionDetails, AccordionSummary, Box, Theme, Typography } from "@mui/material"
import { makeStyles } from "@mui/styles"
import ResponsiveTable, { Column } from "common/ResponsiveTable" import ResponsiveTable, { Column } from "common/ResponsiveTable"
import GrainDescriber from "grain/GrainDescriber" import GrainDescriber from "grain/GrainDescriber"
import { HarvestPlan } from "models" import { cloneDeep } from "lodash"
import { Field, HarvestPlan } from "models"
import { useHarvestPlanAPI } from "providers" import { useHarvestPlanAPI } from "providers"
import { useCallback, useEffect, useState } from "react" import { useCallback, useEffect, useState } from "react"
import { getThemeType } from "theme"
interface Props { interface Props {
field?: string field: Field
} }
const useStyles = makeStyles((theme: Theme) => ({
dark: {
backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(50, 50, 50)",
padding: 5
},
light: {
backgroundColor: getThemeType() === "light" ? "rgb(235, 235, 235)" : "rgb(60, 60, 60)",
padding: 5
}
}));
export default function HarvestPlanTable(props: Props){ export default function HarvestPlanTable(props: Props){
const {field} = props const {field} = props
const harvestAPI = useHarvestPlanAPI() const harvestAPI = useHarvestPlanAPI()
@ -15,9 +31,10 @@ export default function HarvestPlanTable(props: Props){
const [pageSize, setPageSize] = useState(10) const [pageSize, setPageSize] = useState(10)
const [loadedPlans, setLoadedPlans] = useState<HarvestPlan[]>([]) const [loadedPlans, setLoadedPlans] = useState<HarvestPlan[]>([])
const [total, setTotal] = useState(0) const [total, setTotal] = useState(0)
const classes = useStyles()
useEffect(()=>{ useEffect(()=>{
harvestAPI.listHarvestPlans(pageSize, page*pageSize, "asc", "harvestYear", field).then(resp => { harvestAPI.listHarvestPlans(pageSize, page*pageSize, "desc", "harvestYear", field.key()).then(resp => {
if(resp.data.harvestPlan){ if(resp.data.harvestPlan){
setLoadedPlans(resp.data.harvestPlan.map(hp => HarvestPlan.create(hp))) setLoadedPlans(resp.data.harvestPlan.map(hp => HarvestPlan.create(hp)))
} }
@ -106,6 +123,80 @@ export default function HarvestPlanTable(props: Props){
setPage={(page)=>{ setPage={(page)=>{
setPage(page) setPage(page)
}} }}
loadMore={() => {
harvestAPI.listHarvestPlans(pageSize, loadedPlans.length, "desc", "harvestYear", field.key()).then(resp => {
let currentPlans = cloneDeep(loadedPlans)
let newPlans: HarvestPlan[] = []
if(resp.data.harvestPlan){
newPlans = resp.data.harvestPlan.map(hp => HarvestPlan.create(hp))
}
setLoadedPlans(currentPlans.concat(newPlans))
})
}}
renderMobile={(row, index) => {
// since these are more about previous harvest plans i am only showing the final tallys rather than the breakdowns
return (
<Accordion>
<AccordionSummary expandIcon={<ExpandMore />}>
<Typography sx={{fontWeight: 650}}>
{row.settings.title} - {row.harvestYear()}
</Typography>
</AccordionSummary>
<AccordionDetails>
{/* grain */}
<Box className={classes.light} style={{ display: "flex", justifyContent: "space-between" }}>
<Typography variant="subtitle1">Grain:</Typography>
<Typography variant="subtitle1">{GrainDescriber(row.cropType()).name}</Typography>
</Box>
{/* variant */}
<Box className={classes.dark} style={{ display: "flex", justifyContent: "space-between" }}>
<Typography variant="subtitle1">Variant:</Typography>
<Typography variant="subtitle1">{row.grainType()}</Typography>
</Box>
{/* yield target */}
{/* <Box className={classes.dark} style={{ display: "flex", justifyContent: "space-between" }}>
<Typography variant="subtitle1">Yield Target:</Typography>
<Typography variant="subtitle1">{row.yieldTarget()}bu</Typography>
</Box> */}
{/* actual yield */}
<Box className={classes.light} style={{ display: "flex", justifyContent: "space-between" }}>
<Typography variant="subtitle1">Actual Yield:</Typography>
<Typography variant="subtitle1">{row.actualYield()}bu</Typography>
</Box>
{/* bushel price */}
<Box className={classes.dark} style={{ display: "flex", justifyContent: "space-between" }}>
<Typography variant="subtitle1">Bushel Price:</Typography>
<Typography variant="subtitle1">${row.bushelPrice()}</Typography>
</Box>
{/* material cost */}
<Box className={classes.light} style={{ display: "flex", justifyContent: "space-between" }}>
<Typography variant="subtitle1">Material Cost:</Typography>
<Typography variant="subtitle1">${row.totalMaterialCost() * field.acres()}</Typography>
</Box>
{/* equipment cost */}
<Box className={classes.dark} style={{ display: "flex", justifyContent: "space-between" }}>
<Typography variant="subtitle1">Equipment Cost:</Typography>
<Typography variant="subtitle1">${row.totalEquipmentCost() * field.acres()}</Typography>
</Box>
{/* pre-seed materials */}
{/* pre-seed equpment */}
{/* seed materials */}
{/* seed equpment */}
{/* post-seed materials */}
{/* post-seed equpment */}
{/* harvest materials */}
{/* harvest equpment */}
{/* fall materials */}
{/* fall equpment */}
{/* total cost */}
<Box className={classes.light} style={{ display: "flex", justifyContent: "space-between" }}>
<Typography variant="subtitle1">Total Cost:</Typography>
<Typography variant="subtitle1">${(row.totalEquipmentCost() + row.totalMaterialCost()) * field.acres()}</Typography>
</Box>
</AccordionDetails>
</Accordion>
)
}}
/> />
) )
} }

View file

@ -42,11 +42,11 @@ const featureVersions: Map<string, FeatureVersionByPlatform> = new Map([
v2Cell: "N/A", v2Cell: "N/A",
v2WifiS3: "N/A", v2WifiS3: "N/A",
v2CellS3: "N/A", v2CellS3: "N/A",
v2CellBlack: "2.1.6", v2CellBlack: "2.1.7",
v2CellGreen: "N/A", v2CellGreen: "N/A",
v2WifiBlue: "2.1.6", v2WifiBlue: "2.1.7",
v2CellBlue: "2.1.6", v2CellBlue: "2.1.7",
v2EthBlue: "2.1.6" v2EthBlue: "2.1.7"
} }
] ]
]); ]);

View file

@ -1,10 +1,10 @@
import { Field, fieldScope, HarvestPlan, teamScope } from "models"; import { Field, fieldScope, HarvestPlan, teamScope } from "models";
import { useFieldAPI, useGlobalState, useHarvestPlanAPI } from "providers"; import { useFieldAPI, useGlobalState, useHarvestPlanAPI } from "providers";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { useLocation, useParams } from "react-router-dom"; import { useLocation, useNavigate, useParams } from "react-router-dom";
import PageContainer from "./PageContainer"; import PageContainer from "./PageContainer";
import { useMobile, useSnackbar, useUserAPI } from "hooks"; import { useMobile, useSnackbar, useUserAPI } from "hooks";
import { Box, Card, Grid2, IconButton } from "@mui/material"; import { Box, Card, Grid2, IconButton, Tab, Tabs, Theme, Typography, darken } from "@mui/material";
import FieldActions from "field/FieldActions"; import FieldActions from "field/FieldActions";
import { Settings } from "@mui/icons-material"; import { Settings } from "@mui/icons-material";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
@ -14,10 +14,47 @@ import TaskViewer from "tasks/TaskViewer";
import HarvestPlanDisplay from "harvestPlan/HarvestPlanDisplay"; import HarvestPlanDisplay from "harvestPlan/HarvestPlanDisplay";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import HarvestPlanTable from "harvestPlan/HarvestPlanTable"; import HarvestPlanTable from "harvestPlan/HarvestPlanTable";
import { makeStyles } from "@mui/styles";
import FieldSettings from "field/FieldSettings";
const useStyles = makeStyles((theme: Theme) => {
return ({
card: {
height: "100%",
bgcolor: darken(theme.palette.background.paper, 0.05),
},
sectionHeader: {
fontSize: 35,
fontWeight: 650
}
})
})
interface TabPanelProps {
children?: React.ReactNode;
index: any;
value: any;
}
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
aria-labelledby={`simple-tab-${index}`}
style={{ height: "94%", paddingTop: "15px" }}
{...other}>
{value === index && <React.Fragment>{children}</React.Fragment>}
</div>
);
}
export default function FieldPage() { export default function FieldPage() {
const { state } = useLocation(); const { state } = useLocation();
//const [{ as }] = useGlobalState() //const [{ as }] = useGlobalState()
const classes = useStyles();
const fieldAPI = useFieldAPI(); const fieldAPI = useFieldAPI();
const userAPI = useUserAPI(); const userAPI = useUserAPI();
const [{as, user}] = useGlobalState(); const [{as, user}] = useGlobalState();
@ -31,11 +68,23 @@ export default function FieldPage() {
const [hPlan, setHPlan] = useState<HarvestPlan>(HarvestPlan.create()); const [hPlan, setHPlan] = useState<HarvestPlan>(HarvestPlan.create());
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const hPlanAPI = useHarvestPlanAPI(); const hPlanAPI = useHarvestPlanAPI();
const [tabVal, setTabVal] = useState(0)
const navigate = useNavigate()
const loadField = useCallback(() => { const loadField = useCallback(() => {
if(state.field){
fieldAPI.getField(state.field).then(resp => {
setField(Field.create(resp.data))
}).catch(err => {
})
}
},[as]) },[as])
const goTo = () => {
navigate("/fields");
}
useEffect(()=>{ useEffect(()=>{
// if already loading do nothing // if already loading do nothing
if(loading) return if(loading) return
@ -91,13 +140,20 @@ export default function FieldPage() {
const fieldActions = () => { const fieldActions = () => {
return ( return (
<Box> <Grid2 container direction={"row"} justifyContent="space-between" alignContent="center" alignItems="center">
<Grid2>
<Typography className={classes.sectionHeader}>
{field.name()}
</Typography>
</Grid2>
<Grid2>
<IconButton <IconButton
onClick={() => { onClick={() => {
setOpenFieldSettings(true) setOpenFieldSettings(true)
}}><Settings /></IconButton> }}><Settings /></IconButton>
<FieldActions field={field} permissions={permissions} refreshCallback={()=>{}}/> <FieldActions field={field} permissions={permissions} refreshCallback={()=>{}}/>
</Box> </Grid2>
</Grid2>
) )
} }
@ -134,43 +190,44 @@ export default function FieldPage() {
field.key() !== "" && taskLoadKeys.push(field.key()); field.key() !== "" && taskLoadKeys.push(field.key());
//hPlan.key() !== "" && taskLoadKeys.push(hPlan.key()); //hPlan.key() !== "" && taskLoadKeys.push(hPlan.key());
} }
return (<TaskViewer drawerView objectKey={field.key()} loadKeys={taskLoadKeys} />) return (<TaskViewer drawerView objectKey={field.key()} loadKeys={taskLoadKeys} overlayButton={isMobile}/>)
} }
const desktopView = () => { const desktopView = () => {
return ( return (
<React.Fragment> <React.Fragment>
<Box> <Box>
{field.name()}
<Grid2 container spacing={1}> <Grid2 container spacing={1}>
<Grid2 size={4}> <Grid2 size={4}>
<Card raised sx={{height: "100%"}}> <Card raised className={classes.card}>
{map()} {map()}
</Card> </Card>
</Grid2> </Grid2>
<Grid2 size={3}> <Grid2 size={3}>
<Card raised sx={{height: "100%"}}> <Card raised className={classes.card}>
{weather()} {weather()}
</Card> </Card>
</Grid2> </Grid2>
<Grid2 size={5}> <Grid2 size={5}>
<Card raised sx={{height: "100%"}}> <Card raised className={classes.card}>
{tasks()} {tasks()}
</Card> </Card>
</Grid2> </Grid2>
</Grid2> </Grid2>
</Box> </Box>
<Box> <Box>
<Typography className={classes.sectionHeader}>
Harvest Overview Harvest Overview
</Typography>
<Grid2 container spacing={1}> <Grid2 container spacing={1}>
<Grid2 size={4}> <Grid2 size={4}>
<Card raised> <Card raised className={classes.card}>
{harvestPlans()} {harvestPlans()}
</Card> </Card>
</Grid2> </Grid2>
<Grid2 size={8}> <Grid2 size={8}>
<Card> <Card raised>
<HarvestPlanTable field={field.key()}/> <HarvestPlanTable field={field}/>
</Card> </Card>
</Grid2> </Grid2>
</Grid2> </Grid2>
@ -181,12 +238,68 @@ export default function FieldPage() {
const mobileView = () => { const mobileView = () => {
return ( return (
<Box>Mobile will just be the drawer view</Box> <React.Fragment>
<Tabs
value={tabVal}
onChange={(_, val) => {
setTabVal(val)
}}
TabIndicatorProps={{ style: { background: "rgba(255,255,0,255)" } }}>
<Tab label="Overview" />
<Tab label="Activities" />
<Tab label="Harvest" />
</Tabs>
<TabPanel value={tabVal} index={0}>
<Grid2 container spacing={2} direction="column">
<Grid2 size={12}>
<Card raised sx={{height: "50vh"}}>
{map()}
</Card>
</Grid2>
<Grid2 size={12}>
<Card raised>
{weather()}
</Card>
</Grid2>
</Grid2>
</TabPanel>
<TabPanel value={tabVal} index={1}>
<Card raised>
{tasks()}
</Card>
</TabPanel>
<TabPanel value={tabVal} index={2}>
<Grid2>
<Grid2 size={12}>
<Card raised className={classes.card}>
{harvestPlans()}
</Card>
</Grid2>
<Grid2>
<HarvestPlanTable field={field}/>
</Grid2>
</Grid2>
</TabPanel>
</React.Fragment>
) )
} }
const fieldSettings = () => { const fieldSettings = () => {
return (
<FieldSettings
open={openFieldSettings}
onClose={() => {
setOpenFieldSettings(false)
}}
selectedField={field}
updateFields={() => {
loadField()
}}
removeField={()=>{
goTo()
}}
/>
)
} }
return ( return (
@ -195,6 +308,7 @@ export default function FieldPage() {
{fieldActions()} {fieldActions()}
{isMobile ? mobileView() : desktopView()} {isMobile ? mobileView() : desktopView()}
</Box> </Box>
{fieldSettings()}
</PageContainer> </PageContainer>
) )
} }