finished putting in the table and the dropdowns and the loading of stuff when it is needed
This commit is contained in:
parent
fffa8edc46
commit
49bd146de6
2 changed files with 331 additions and 19 deletions
|
|
@ -1,10 +1,19 @@
|
||||||
import { Box } from "@mui/material";
|
import { Autocomplete, Box, Button, Card, DialogActions, DialogContent, DialogTitle, TextField, Typography } from "@mui/material";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
import ResponsiveTable from "common/ResponsiveTable";
|
import ResponsiveTable from "common/ResponsiveTable";
|
||||||
import { Bin } from "models";
|
import { Option } from "common/SearchSelect";
|
||||||
|
import GrainDescriber from "grain/GrainDescriber";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import { Bin, Contract, Field, GrainBag } from "models";
|
||||||
import { Transaction } from "models/Transaction";
|
import { Transaction } from "models/Transaction";
|
||||||
|
import moment from "moment";
|
||||||
|
import ObjectDescriber from "objects/ObjectDescriber";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { useTransactionAPI } from "providers";
|
import { useBinAPI, useContractAPI, useFieldAPI, useGrainBagAPI, useTransactionAPI } from "providers";
|
||||||
|
import React from "react";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import TransactionDataDisplay from "transactions/transactionDataDisplay";
|
||||||
|
import { getGrainUnit } from "utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
bin: Bin
|
bin: Bin
|
||||||
|
|
@ -12,32 +21,331 @@ interface Props {
|
||||||
|
|
||||||
export default function BinTransactions(props: Props){
|
export default function BinTransactions(props: Props){
|
||||||
const { bin } = props
|
const { bin } = props
|
||||||
const [state, setState] = useState<pond.TransactionState>(pond.TransactionState.TRANSACTION_STATE_PENDING)
|
const [state, setState] = useState<pond.TransactionState>(pond.TransactionState.TRANSACTION_STATE_APPROVAL_REQUIRED)
|
||||||
const transactionAPI = useTransactionAPI()
|
const transactionAPI = useTransactionAPI()
|
||||||
|
const binAPI = useBinAPI()
|
||||||
|
const grainBagAPI = useGrainBagAPI()
|
||||||
|
const fieldAPI = useFieldAPI()
|
||||||
|
const contractAPI = useContractAPI()
|
||||||
const [transactions, setTransactions] = useState<Transaction[]>([])
|
const [transactions, setTransactions] = useState<Transaction[]>([])
|
||||||
|
const [possibleTransactionMatches, setPossibleTransactionMatches] = useState<Transaction[]>([])
|
||||||
|
const [tablePage, setTablePage] = useState(0)
|
||||||
|
const [tablePageSize, setTablePageSize] = useState(10)
|
||||||
|
const [total, setTotal] = useState(0)
|
||||||
|
|
||||||
const load = useCallback(()=>{
|
const [binOptions, setBinOptions] = useState<Option[]>([])
|
||||||
// load transactions for bin with matching state (pending is default)
|
|
||||||
transactionAPI.listTransactionsFor(10, 0, "asc", bin.key(), state).then(resp => {
|
const [grainBagOptions, setGrainBagOptions] = useState<Option[]>([])
|
||||||
console.log(resp)
|
|
||||||
|
const [fieldOptions, setFieldOptions] = useState<Option[]>([])
|
||||||
|
|
||||||
|
const [contractOptions, setContractOptions] = useState<Option[]>([])
|
||||||
|
|
||||||
|
const [openDialog, setOpenDialog] = useState(false)
|
||||||
|
const [objectNames, setObjectNames] = useState(new Map<string, string>())
|
||||||
|
const [selectedTransaction, setSelectedTransaction] = useState<Transaction>()
|
||||||
|
const [selectedMatch, setselectedMatch] = useState<Transaction>()
|
||||||
|
|
||||||
|
const [fromType, setFromType] = useState<pond.ObjectType>(pond.ObjectType.OBJECT_TYPE_UNKNOWN)
|
||||||
|
const [fromOption, setFromOption] = useState<Option | undefined>()
|
||||||
|
|
||||||
|
const [toType, setToType] = useState<pond.ObjectType>(pond.ObjectType.OBJECT_TYPE_UNKNOWN)
|
||||||
|
const [toOption, setToOption] = useState<Option | undefined>()
|
||||||
|
|
||||||
|
//the following object loads are loading the objects for options when editing transactions to accept as well as build the name map for display
|
||||||
|
//these only happen when going to the transactions tab on the bin page
|
||||||
|
|
||||||
|
//load bins
|
||||||
|
const loadBins = ()=>{
|
||||||
|
//limit of 0 will load them all but this load will only happen once regardless of how many times the dropdown for type is changed
|
||||||
|
binAPI.listBins(0, 0, "asc", "name").then(resp => {
|
||||||
|
setBinOptions(resp.data.bins.map(b => {
|
||||||
|
let bin = Bin.create(b)
|
||||||
|
return {label: bin.name(), value: bin.key()}
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
},[state, bin])
|
}
|
||||||
|
|
||||||
|
//load grainbags
|
||||||
|
const loadGrainbags = ()=>{
|
||||||
|
grainBagAPI.listGrainBags(0, 0, "asc", "name").then(resp => {
|
||||||
|
setGrainBagOptions(resp.data.grainBags.map(g => {
|
||||||
|
let grainBag = GrainBag.create(g)
|
||||||
|
return {label: grainBag.name(), value: grainBag.key()}
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//load fields
|
||||||
|
const loadFields = useCallback(()=>{
|
||||||
|
|
||||||
|
},[])
|
||||||
|
|
||||||
|
//load contracts
|
||||||
|
const loadContracts = useCallback(()=>{
|
||||||
|
|
||||||
|
},[])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const loadTransactionsFor = useCallback(()=>{
|
||||||
|
// load transactions for bin with matching state (pending is default)
|
||||||
|
transactionAPI.listTransactionsFor(tablePageSize, tablePage * tablePageSize, "asc", bin.key(), state).then(resp => {
|
||||||
|
setTotal(resp.data.total)
|
||||||
|
// console.log(resp.data)
|
||||||
|
setTransactions(resp.data.transactions.map(t => Transaction.create(t)))
|
||||||
|
//TODO: for now just use the bin for the name map, once a backend method of getting the names is implemented the response can be used to build the map
|
||||||
|
let nameMap = new Map<string, string>()
|
||||||
|
nameMap.set(bin.key(), bin.name())
|
||||||
|
setObjectNames(nameMap)
|
||||||
|
})
|
||||||
|
},[state, bin, tablePage, tablePageSize])
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
load()
|
loadTransactionsFor()
|
||||||
},[state, bin, load])
|
},[state, bin, loadTransactionsFor])
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
//submit approval
|
||||||
|
//in the response if it succeeded remove (not reject but fully delete it) the matched transaction
|
||||||
|
}
|
||||||
|
|
||||||
|
const reject = () => {
|
||||||
|
//reject the transaction
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOptions = (type: pond.ObjectType) => {
|
||||||
|
switch(type){
|
||||||
|
case pond.ObjectType.OBJECT_TYPE_BIN:
|
||||||
|
return binOptions
|
||||||
|
case pond.ObjectType.OBJECT_TYPE_GRAIN_BAG:
|
||||||
|
return grainBagOptions
|
||||||
|
default:
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// dialog for approving a transaction
|
// dialog for approving a transaction
|
||||||
|
const approvalDialog = () => {
|
||||||
|
// get the display information for the transaction
|
||||||
|
const grainTransaction = selectedTransaction?.transaction.transaction?.grainTransaction ?? pond.GrainTransaction.create()
|
||||||
|
const bushels = grainTransaction.bushels ?? 0
|
||||||
|
const weight = bushels / grainTransaction.bushelsPerTonne
|
||||||
|
let graintype = grainTransaction.grainType ?? pond.Grain.GRAIN_INVALID
|
||||||
|
const grainDisplay = graintype === pond.Grain.GRAIN_CUSTOM
|
||||||
|
? grainTransaction.customGrain
|
||||||
|
: GrainDescriber(grainTransaction.grainType).name
|
||||||
|
|
||||||
|
//determine which location (from/to) that can be edited
|
||||||
|
let fromDisabled = selectedTransaction?.transaction.fromKey !== ""
|
||||||
|
let toDisabled = selectedTransaction?.transaction.toKey !== ""
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog open={openDialog} onClose={()=>{setOpenDialog(false)}}>
|
||||||
|
<DialogTitle>Accept Transaction</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Box>
|
||||||
|
<Typography>{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? weight + " mT" : bushels + " bushels"} of {grainTransaction.subtype} {grainDisplay} moved</Typography>
|
||||||
|
<Typography>From</Typography>
|
||||||
|
{/*Object type selection*/}
|
||||||
|
<Autocomplete
|
||||||
|
disabled={fromDisabled}
|
||||||
|
id="autoFromTypes"
|
||||||
|
value={fromType}
|
||||||
|
options={[
|
||||||
|
pond.ObjectType.OBJECT_TYPE_BIN,
|
||||||
|
pond.ObjectType.OBJECT_TYPE_GRAIN_BAG,
|
||||||
|
|
||||||
|
]}
|
||||||
|
getOptionLabel={(option: pond.ObjectType) => ObjectDescriber(option).name}
|
||||||
|
fullWidth
|
||||||
|
onChange={(e, val) => {
|
||||||
|
setFromType(val ?? pond.ObjectType.OBJECT_TYPE_UNKNOWN)
|
||||||
|
//based on what it is changed to need to load data if it is not already loaded
|
||||||
|
if(val === pond.ObjectType.OBJECT_TYPE_BIN){
|
||||||
|
//if there is data already the initial load already happened
|
||||||
|
if(binOptions.length === 0){
|
||||||
|
//do the initial load of options
|
||||||
|
loadBins()
|
||||||
|
}
|
||||||
|
}else if (val === pond.ObjectType.OBJECT_TYPE_GRAIN_BAG){
|
||||||
|
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
renderInput={params => <TextField {...params} label="Type" />}
|
||||||
|
/>
|
||||||
|
{/* object selection */}
|
||||||
|
<Autocomplete
|
||||||
|
disabled={fromDisabled}
|
||||||
|
id="autoFromObjects"
|
||||||
|
value={fromOption}
|
||||||
|
options={getOptions(fromType)}
|
||||||
|
getOptionLabel={(option: Option) => option.label}
|
||||||
|
fullWidth
|
||||||
|
onChange={(e, val) => {
|
||||||
|
setFromOption(val ?? undefined)
|
||||||
|
//once one is selected get the transactions for it that are requiring approval
|
||||||
|
transactionAPI.listTransactionsFor(0, 0, "asc", val?.value, pond.TransactionState.TRANSACTION_STATE_APPROVAL_REQUIRED).then(resp => {
|
||||||
|
let matches: Transaction[] = []
|
||||||
|
resp.data.transactions.forEach(transaction => {
|
||||||
|
//the transaction could be a match if the from is the object you selected and the to is unknown
|
||||||
|
if(transaction.fromKey === val?.value && transaction.toKey === ""){
|
||||||
|
matches.push(Transaction.create(transaction))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setPossibleTransactionMatches(matches)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
renderInput={params => <TextField {...params} label="From" />}
|
||||||
|
/>
|
||||||
|
<Typography>To</Typography>
|
||||||
|
<Autocomplete
|
||||||
|
disabled={toDisabled}
|
||||||
|
id="autoFromTypes"
|
||||||
|
value={toType}
|
||||||
|
options={[
|
||||||
|
pond.ObjectType.OBJECT_TYPE_BIN,
|
||||||
|
pond.ObjectType.OBJECT_TYPE_GRAIN_BAG,
|
||||||
|
|
||||||
|
]}
|
||||||
|
getOptionLabel={(option: pond.ObjectType) => ObjectDescriber(option).name}
|
||||||
|
fullWidth
|
||||||
|
onChange={(e, val) => {
|
||||||
|
setToType(val ?? pond.ObjectType.OBJECT_TYPE_UNKNOWN)
|
||||||
|
//based on what it is changed to need to load data if it is not already loaded
|
||||||
|
if(val === pond.ObjectType.OBJECT_TYPE_BIN){
|
||||||
|
//if there is data already the initial load already happened
|
||||||
|
if(binOptions.length === 0){
|
||||||
|
//do the initial load of options
|
||||||
|
loadBins()
|
||||||
|
}
|
||||||
|
}else if (val === pond.ObjectType.OBJECT_TYPE_GRAIN_BAG){
|
||||||
|
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
renderInput={params => <TextField {...params} label="Type" />}
|
||||||
|
/>
|
||||||
|
<Autocomplete
|
||||||
|
disabled={toDisabled}
|
||||||
|
id="autoFromObjects"
|
||||||
|
value={toOption}
|
||||||
|
options={getOptions(toType)}
|
||||||
|
getOptionLabel={(option: Option) => option.label}
|
||||||
|
fullWidth
|
||||||
|
onChange={(e, val) => {
|
||||||
|
//once one is selected get the transactions for it that are requiring approval
|
||||||
|
transactionAPI.listTransactionsFor(0, 0, "asc", val?.value, pond.TransactionState.TRANSACTION_STATE_APPROVAL_REQUIRED).then(resp => {
|
||||||
|
let matches: Transaction[] = []
|
||||||
|
resp.data.transactions.forEach(transaction => {
|
||||||
|
//the transaction could be a match if the to is the object selected and the from is unknown
|
||||||
|
if(transaction.toKey === val?.value && transaction.fromKey === ""){
|
||||||
|
matches.push(Transaction.create(transaction))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setPossibleTransactionMatches(matches)
|
||||||
|
})
|
||||||
|
|
||||||
|
}}
|
||||||
|
renderInput={params => <TextField {...params} label="From" />}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
{possibleTransactionMatches.map(t => {
|
||||||
|
return (
|
||||||
|
<Typography>
|
||||||
|
{t.transaction.timestamp}
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant="contained">Close</Button>
|
||||||
|
<Button variant="contained" color="error">Reject</Button>
|
||||||
|
<Button variant="contained" color="primary">Approve</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<React.Fragment>
|
||||||
{/* dropdown for transaction state */}
|
{approvalDialog()}
|
||||||
|
<Card>
|
||||||
|
{/* dropdown for transaction state
|
||||||
|
--FUTURE THING--
|
||||||
|
when we can implement a better way to get the current names of objects involved
|
||||||
|
other than loading everything up front
|
||||||
|
*/}
|
||||||
|
|
||||||
{/* table to display transactions */}
|
{/* table to display transactions */}
|
||||||
{/* <ResponsiveTable<Transaction>
|
<ResponsiveTable<Transaction>
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: "Date",
|
||||||
|
render: row => {
|
||||||
|
return (
|
||||||
|
<Box padding={2}>
|
||||||
|
<Typography>{moment(row.transaction.timestamp).format("YYYY-MM-DD")}</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "From",
|
||||||
|
render: row => {
|
||||||
|
return (
|
||||||
|
<Box padding={2}>
|
||||||
|
<Typography>{ObjectDescriber(row.transaction.fromObject).name}</Typography>
|
||||||
|
<Typography>
|
||||||
|
{objectNames ? objectNames.get(row.transaction.fromKey) : row.transaction.fromKey}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "To",
|
||||||
|
render: row => {
|
||||||
|
return (
|
||||||
|
<Box padding={2}>
|
||||||
|
<Typography>{ObjectDescriber(row.transaction.toObject).name}</Typography>
|
||||||
|
<Typography>
|
||||||
|
{objectNames ? objectNames.get(row.transaction.toKey) : row.transaction.toKey}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Transaction",
|
||||||
|
render: row => {
|
||||||
|
return (
|
||||||
|
<Box padding={2}>
|
||||||
|
<TransactionDataDisplay transaction={row} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
title="Pending Transactions"
|
||||||
|
subtitle={"Select a transaction to approve"}
|
||||||
|
noDataMessage="No Pending Transactions Found"
|
||||||
|
onRowClick={(row)=> {
|
||||||
|
setSelectedTransaction(row)
|
||||||
|
setFromType(row.transaction.fromObject)
|
||||||
|
setFromOption({label: objectNames.get(row.transaction.fromKey) ?? "", value: row.transaction.fromKey})
|
||||||
|
setToType(row.transaction.toObject)
|
||||||
|
setToOption({label: objectNames.get(row.transaction.toKey) ?? "", value: row.transaction.toKey})
|
||||||
|
setOpenDialog(true)
|
||||||
|
}}
|
||||||
rows={transactions}
|
rows={transactions}
|
||||||
/> */}
|
handleRowsPerPageChange={(e)=>{setTablePageSize(e.target.value)}}
|
||||||
</Box>
|
setPage={(page)=>{setTablePage(page)}}
|
||||||
|
page={tablePage}
|
||||||
|
pageSize={tablePageSize}
|
||||||
|
total={total}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1000,8 +1000,9 @@ export default function Bin(props: Props) {
|
||||||
<Tab label="Inventory" style={{ fontWeight: mobileTab === 1 ? 650 : 500 }} />
|
<Tab label="Inventory" style={{ fontWeight: mobileTab === 1 ? 650 : 500 }} />
|
||||||
<Tab label="Sensors" style={{ fontWeight: mobileTab === 2 ? 650 : 500 }} />
|
<Tab label="Sensors" style={{ fontWeight: mobileTab === 2 ? 650 : 500 }} />
|
||||||
<Tab label="Analysis" style={{ fontWeight: mobileTab === 3 ? 650 : 500 }} />
|
<Tab label="Analysis" style={{ fontWeight: mobileTab === 3 ? 650 : 500 }} />
|
||||||
<Tab label="Alerts" style={{ fontWeight: mobileTab === 3 ? 650 : 500 }} />
|
<Tab label="Alerts" style={{ fontWeight: mobileTab === 4 ? 650 : 500 }} />
|
||||||
<Tab label="Presets" style={{ fontWeight: mobileTab === 3 ? 650 : 500 }} />
|
<Tab label="Presets" style={{ fontWeight: mobileTab === 5 ? 650 : 500 }} />
|
||||||
|
<Tab label="Transactions" style={{ fontWeight: mobileTab === 6 ? 650 : 500 }} />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<TabPanelMine value={mobileTab} index={0}>
|
<TabPanelMine value={mobileTab} index={0}>
|
||||||
{overview()}
|
{overview()}
|
||||||
|
|
@ -1088,6 +1089,9 @@ export default function Bin(props: Props) {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TabPanelMine>
|
</TabPanelMine>
|
||||||
|
<TabPanelMine value={mobileTab} index={6}>
|
||||||
|
<BinTransactions bin={bin}/>
|
||||||
|
</TabPanelMine>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue