diff --git a/src/grain/CustomGrainForm.tsx b/src/grain/CustomGrainForm.tsx index 2a0379a..905931c 100644 --- a/src/grain/CustomGrainForm.tsx +++ b/src/grain/CustomGrainForm.tsx @@ -1,7 +1,7 @@ import {MenuItem, TextField } from "@mui/material" import { cloneDeep } from "lodash" import { pond } from "protobuf-ts/pond" -import React, { useEffect, useState } from "react" +import React, { useEffect, useRef, useState } from "react" interface Props { grainSettings?: pond.GrainSettings @@ -19,6 +19,7 @@ export default function CustomGrainForm(props: Props) { const [constantC, setConstantC] = useState("0") const [kgPerBushel, setKgPerBushel] = useState("0") const [bushelsPerTonne, setBushelsPerTonne] = useState("0") + const valid = useRef(false) useEffect(()=>{ if(grainSettings){ @@ -34,20 +35,20 @@ export default function CustomGrainForm(props: Props) { } },[grainSettings]) - const invalid = () => { - if (name === "") return true - if (group === "") return true - if (isNaN(parseFloat(constantA))) return true - if (isNaN(parseFloat(constantB))) return true - if (isNaN(parseFloat(constantA))) return true - if (isNaN(parseFloat(kgPerBushel))) return true - if (isNaN(parseFloat(bushelsPerTonne))) return true - return false + const validate = (name: string, group: string, constA: string, constB: string, constC: string, kgPerBushel: string, bushelsPerTonne: string) => { + if (name === "") return false + if (group === "") return false + if (isNaN(parseFloat(constA))) return false + if (isNaN(parseFloat(constB))) return false + if (isNaN(parseFloat(constC))) return false + if (isNaN(parseFloat(kgPerBushel))) return false + if (isNaN(parseFloat(bushelsPerTonne))) return false + return true } const settingsChanged = (newSettings: pond.GrainSettings) => { setNewGrainSettings(newSettings) - onGrainSettingsChange(newSettings, !invalid()) + onGrainSettingsChange(newSettings, valid.current) } const grainForm = () => { @@ -63,6 +64,7 @@ export default function CustomGrainForm(props: Props) { setName(name) let settings = cloneDeep(newGrainSettings) settings.name = name + valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, bushelsPerTonne) settingsChanged(settings) }}/> @@ -117,11 +121,12 @@ export default function CustomGrainForm(props: Props) { onChange={(e) => { let val = e.target.value setConstantA(val) + valid.current = validate(name, group, val, constantB, constantC, kgPerBushel, bushelsPerTonne) + let settings = cloneDeep(newGrainSettings) if(!isNaN(parseFloat(val))){ - let settings = cloneDeep(newGrainSettings) settings.a = parseFloat(val) - settingsChanged(settings) } + settingsChanged(settings) }} label="Constant A"/> { let val = e.target.value setConstantB(val) + valid.current = validate(name, group, constantA, val, constantC, kgPerBushel, bushelsPerTonne) + let settings = cloneDeep(newGrainSettings) if(!isNaN(parseFloat(val))){ - let settings = cloneDeep(newGrainSettings) settings.b = parseFloat(val) - settingsChanged(settings) } + settingsChanged(settings) }} label="Constant B"/> { let val = e.target.value setConstantC(val) + valid.current = validate(name, group, constantA, constantB, val, kgPerBushel, bushelsPerTonne) + let settings = cloneDeep(newGrainSettings) if(!isNaN(parseFloat(val))){ - let settings = cloneDeep(newGrainSettings) settings.c = parseFloat(val) - settingsChanged(settings) } + settingsChanged(settings) }} label="Constant C"/> { let val = e.target.value setKgPerBushel(val) + valid.current = validate(name, group, constantA, constantB, constantC, val, bushelsPerTonne) + let settings = cloneDeep(newGrainSettings) if(!isNaN(parseFloat(val))){ - let settings = cloneDeep(newGrainSettings) settings.kgPerBushel = parseFloat(val) - settingsChanged(settings) } + settingsChanged(settings) }} label="kg per Bushel"/> { let val = e.target.value setBushelsPerTonne(val) + valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, val) + let settings = cloneDeep(newGrainSettings) if(!isNaN(parseFloat(val))){ - let settings = cloneDeep(newGrainSettings) settings.bushelsPerTonne = parseFloat(val) - settingsChanged(settings) } + settingsChanged(settings) }} label="Bushels per Tonne"/> diff --git a/src/pages/Grains.tsx b/src/pages/Grains.tsx index 59fbead..2ff47eb 100644 --- a/src/pages/Grains.tsx +++ b/src/pages/Grains.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from "react"; import PageContainer from "./PageContainer"; import CustomGrainForm from "grain/CustomGrainForm"; -import { Box, Button, DialogActions, DialogContent, DialogTitle, Grid2, Typography } from "@mui/material"; +import { Accordion, AccordionDetails, AccordionSummary, Box, Button, DialogActions, DialogContent, DialogTitle, Divider, Grid2, IconButton, Typography } from "@mui/material"; import { pond } from "protobuf-ts/pond"; import ButtonGroup from "common/ButtonGroup"; import { useMobile, useSnackbar, useUserAPI } from "hooks"; @@ -11,6 +11,8 @@ import { GetGrainExtensionMap, GetValidGrainExtensions, GrainExtension } from "g import ResponsiveDialog from "common/ResponsiveDialog"; import { useGlobalState } from "providers"; import { Scope } from "models"; +import React from "react"; +import { AddCircle, ExpandMore } from "@mui/icons-material"; export default function Grains() { const [currentCustomGrain, setCurrentCustomGrain] = useState() @@ -18,7 +20,7 @@ export default function Grains() { const [selectedGrain, setSelectedGrain] = useState() const [displayCustom, setDisplayCustom] = useState(false) const { openSnack } = useSnackbar() - const [tableSize, setTableSize] = useState(5) + const [tableSize, setTableSize] = useState(10) const [tablePage, setTablePage] = useState(0) const [tableTotal, setTableTotal] = useState(0) const grainAPI = useGrainAPI() @@ -29,7 +31,11 @@ export default function Grains() { const [openUpdateConfirmation, setOpenUpdateConfirmation] = useState(false) const [grainObjectTableData, setGrainObjectTableData] = useState([]) const [supportedGrainTableData, setSupportedGrainTableData] = useState([]) + // const [selectedGrainExtension, setSelectedGrainExtension] = useState() + const [accordionKey, setAccordionKey] = useState("allClosed") + const [openNewGrainDialog, setOpenNewGrainDialog] = useState(false) const isMobile = useMobile() + const extraLoad = 10 //the amount of more to load on the mobile view when load more is clicked //function to load the first set of custom grains const loadCustomGrain = useCallback(() => { @@ -92,8 +98,25 @@ export default function Grains() { return ( rows={grainObjectTableData} + noDataMessage="No Grain Types Found. Add a new custom grain to see them here" + loadMore={() => { + setTableSize(tableSize + extraLoad) + }} + renderMobile={(row) => { + //close it if it is already open + if(accordionKey === row.key){ + setAccordionKey("allClosed") + }else{ + setAccordionKey(row.key) + } + setCurrentCustomGrain(row.settings ?? pond.GrainSettings.create()) + }}> + }>{row.name} + + {customGrainDisplay()} + + } onRowClick={(grainObj) => { - setCurrentCustomGrain(grainObj.settings ?? pond.GrainSettings.create()) setSelectedGrain(grainObj) setFormValid(true) /** if they are viewin as a team leave the permissions alone, if not then use the users permissions to the grain type, @@ -108,7 +131,7 @@ export default function Grains() { columns={[ { title: "Name", - render: row => + render: row => {row.name} @@ -116,7 +139,7 @@ export default function Grains() { }, { title: "Group", - render: row => + render: row => {row.settings?.group} @@ -124,7 +147,7 @@ export default function Grains() { }, { title: "Equation", - render: row => + render: row => {equationName(row.settings?.equation ?? 0)} @@ -150,11 +173,21 @@ export default function Grains() { rows={supportedGrainTableData} page={tablePage} + // onRowClick={(ext) => {setSelectedGrainExtension(ext)}} + loadMore={() => { + setTableSize(tableSize + extraLoad) + }} + renderMobile={(row) => + }>{row.name} + + {supportedDisplay(row)} + + } pageSize={tableSize} - columns={[ + columns={[ { title: "Name", - render: row => + render: row => {row.name} @@ -162,7 +195,7 @@ export default function Grains() { }, { title: "Group", - render: row => + render: row => {row.group} @@ -170,11 +203,27 @@ export default function Grains() { }, { title: "Equation", - render: row => + render: row => {equationName(row.equation ?? 0)} + }, + { + title: "Kg Per Bushel", + render: row => + + {row.weightConversionKg.toFixed(2)} + + + }, + { + title: "Bushels Per Tonne", + render: row => + + {row.bushelsPerTonne.toFixed(2)} + + } ]} handleRowsPerPageChange={(e) => { @@ -189,9 +238,42 @@ export default function Grains() { } //the grain display for supported grain type to show the rest of the data such as what constants are used in the formula - const supportedDisplay = () => { + const supportedDisplay = (grain?: GrainExtension) => { return ( - + + {grain ? + + + Info + + + + Name: {grain.name} + Group: {grain.group} + + + Formula + + + + Equation: {equationName(grain.equation)} + {/* Constant A: {grain.a} + Constant B: {grain.b} + Constant C: {grain.c} */} + + + Conversions + + + + Kg per Bushel: {grain.weightConversionKg.toFixed(2)} + Bushels per Tonne: {grain.bushelsPerTonne.toFixed(2)} + + + : + Select a Grain from the table to view its details + } + ) } @@ -201,6 +283,9 @@ export default function Grains() { openSnack("Grain type updated") }).catch(err => { openSnack("There was an issue updating the selected grain type") + }).finally(() => { + setOpenUpdateConfirmation(false) + loadCustomGrain() }) } } @@ -221,6 +306,19 @@ export default function Grains() { ) } + //because the form is embedded on desktop this dialog is specifically for mobile view + const newGrainDialog = () => { + return ( + setOpenNewGrainDialog(false)}> + Add New Grain + + {customGrainDisplay()} + + + + ) + } + const addNewGrain = () => { if(currentCustomGrain){ grainAPI.addGrain(currentCustomGrain).then(resp => { @@ -228,6 +326,9 @@ export default function Grains() { openSnack("New grain type added") }).catch(err => { openSnack("There was a problem creating the new grain type") + }).finally(() => { + loadCustomGrain() + setOpenNewGrainDialog(false)//only for the mobile view since the desktop has it embedded }) } } @@ -238,6 +339,9 @@ export default function Grains() { openSnack("Grain type removed") }).catch(err => { openSnack("There was a problem removing the selected grain type") + }).finally(() => { + setOpenRemoveConfirmation(false) + loadCustomGrain() }) } } @@ -272,7 +376,8 @@ export default function Grains() { {selectedGrain && } - + {isMobile && !selectedGrain && } + ) @@ -283,7 +388,7 @@ export default function Grains() { return ( { setCurrentCustomGrain(newSettings) setFormValid(formValid) @@ -295,30 +400,41 @@ export default function Grains() { } const desktopView = () => { - return ( + if(displayCustom){ + return ( - {displayCustom ? grainObjectTable() : supportedGrainTable()} + {grainObjectTable()} - {displayCustom ? customGrainDisplay() : supportedDisplay()} + {customGrainDisplay()} - ) + ) + }else{ + return ( + + {supportedGrainTable()} + + ) + } } const mobileView = () => { return ( - + + {displayCustom ? grainObjectTable() : supportedGrainTable()} + ) } return ( - - GRAIN PAGE + + + Adaptive Grains - + + {isMobile && displayCustom && + { + setSelectedGrain(undefined) + setCurrentCustomGrain(undefined) + setOpenNewGrainDialog(true) + }}> + + + } {!isMobile ? desktopView() : mobileView()} + {updateGrainConfirmation()} {removeConfirmation()} + {newGrainDialog()} ) } \ No newline at end of file