Merge branch 'hybrid_inventory_control' into staging_environment
This commit is contained in:
commit
cb7bf64c68
15 changed files with 729 additions and 38 deletions
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -42,7 +42,7 @@
|
|||
"mui-tel-input": "^7.0.0",
|
||||
"notistack": "^3.0.1",
|
||||
"openweathermap-ts": "^1.2.10",
|
||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging",
|
||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#hybrid_inventory_control",
|
||||
"react": "^18.3.1",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-color": "^2.19.3",
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ export default function BinSettings(props: Props) {
|
|||
const [inMoistureString, setInMoistureString] = useState("0");
|
||||
const [tMoistureString, setTMoistureString] = useState("0");
|
||||
const [moistureTargetDeviation, setMoistureTargetDeviation] = useState("0");
|
||||
const [autoFillThreshold, setAutoFillThreshold] = useState(0);
|
||||
const [validInitial, setValidInitial] = useState(true);
|
||||
const [validTarget, setValidTarget] = useState(true);
|
||||
const [validDeviation, setValidDeviation] = useState(true);
|
||||
|
|
@ -311,6 +312,7 @@ export default function BinSettings(props: Props) {
|
|||
setInMoistureString(initForm.inventory?.initialMoisture.toString() ?? "0");
|
||||
setTMoistureString(initForm.inventory?.targetMoisture.toString() ?? "0");
|
||||
setMoistureTargetDeviation(initForm.inventory?.moistureTargetDeviation.toString() ?? "0");
|
||||
setAutoFillThreshold(initForm.inventory?.autoThreshold ?? 0)
|
||||
setActiveStep(0);
|
||||
if (bin) {
|
||||
setAutoTopNode(bin.settings.autoGrainNode);
|
||||
|
|
@ -405,7 +407,10 @@ export default function BinSettings(props: Props) {
|
|||
form.highTemp = highTempC;
|
||||
form.lowTemp = lowTempC;
|
||||
form.autoGrainNode = autoTopNode;
|
||||
if (form.inventory) form.inventory.inventoryControl = inventoryControl;
|
||||
if (form.inventory) {
|
||||
form.inventory.inventoryControl = inventoryControl
|
||||
form.inventory.autoThreshold = autoFillThreshold
|
||||
}
|
||||
|
||||
binAPI
|
||||
.addBin(form, as)
|
||||
|
|
@ -449,7 +454,10 @@ export default function BinSettings(props: Props) {
|
|||
form.highTemp = highTempC;
|
||||
form.lowTemp = lowTempC;
|
||||
form.autoGrainNode = autoTopNode;
|
||||
if (form.inventory) form.inventory.inventoryControl = inventoryControl;
|
||||
if (form.inventory) {
|
||||
form.inventory.inventoryControl = inventoryControl
|
||||
form.inventory.autoThreshold = autoFillThreshold
|
||||
}
|
||||
binAPI
|
||||
.updateBin(bin.key(), form, as)
|
||||
.then(response => {
|
||||
|
|
@ -698,11 +706,16 @@ export default function BinSettings(props: Props) {
|
|||
};
|
||||
|
||||
const inventoryControlLabel = (control: pond.BinInventoryControl) => {
|
||||
let string = "Manual";
|
||||
if (control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC) {
|
||||
string = "Auto";
|
||||
switch(control){
|
||||
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR:
|
||||
return "Auto (lidar)"
|
||||
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC:
|
||||
return "Auto (cable)"
|
||||
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR:
|
||||
return "Hybrid (lidar)"
|
||||
default:
|
||||
return "Manual"
|
||||
}
|
||||
return string;
|
||||
};
|
||||
|
||||
const inventoryForm = () => {
|
||||
|
|
@ -818,6 +831,7 @@ export default function BinSettings(props: Props) {
|
|||
<Accordion className={classes.bottomSpacing}>
|
||||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||||
<Grid
|
||||
width={"100%"}
|
||||
container
|
||||
direction="row"
|
||||
alignContent="center"
|
||||
|
|
@ -853,6 +867,11 @@ export default function BinSettings(props: Props) {
|
|||
control={<Radio />}
|
||||
label={"Auto (Lidar)"}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR}
|
||||
control={<Radio />}
|
||||
label={"Hybrid (Lidar)"}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</AccordionDetails>
|
||||
{inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC &&
|
||||
|
|
@ -863,6 +882,36 @@ export default function BinSettings(props: Props) {
|
|||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR && (
|
||||
<Box marginX={2} marginBottom={2}>
|
||||
<Typography variant="caption">
|
||||
Hybrid control will submit a transaction that must be approved by the user before inventory is changed
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{(inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
|
||||
inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR) &&
|
||||
<Box width="100%" padding={2}>
|
||||
<TextField
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
label="Auto Threshold Percent"
|
||||
helperText="Fill percent threshold that must be crossed to affect inventory"
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
%
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
type="number"
|
||||
value={autoFillThreshold}
|
||||
onChange={e => {
|
||||
setAutoFillThreshold(+e.target.value)
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
</Accordion>
|
||||
{empty ? (
|
||||
<Box marginTop={3} display="flex" flexDirection="column" alignItems="center">
|
||||
|
|
@ -1814,8 +1863,9 @@ export default function BinSettings(props: Props) {
|
|||
</Box>
|
||||
|
||||
<Box marginTop={2}>
|
||||
<FormControl component="fieldset" fullWidth disabled={!canEdit}>
|
||||
<OutlinedBox label={<FormLabel component="legend">Shape</FormLabel>} padding={1}>
|
||||
<FormControl component="fieldset" variant="outlined" fullWidth disabled={!canEdit}>
|
||||
{/* <OutlinedBox label={<FormLabel component="legend">Shape</FormLabel>} padding={1}> */}
|
||||
<FormLabel component="legend">Shape</FormLabel>
|
||||
<RadioGroup
|
||||
aria-label="bin shape"
|
||||
name="binShape"
|
||||
|
|
@ -1868,7 +1918,7 @@ export default function BinSettings(props: Props) {
|
|||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</OutlinedBox>
|
||||
{/* </OutlinedBox> */}
|
||||
<FormHelperText>Aeration effectiveness varies with bin shape</FormHelperText>
|
||||
</FormControl>
|
||||
</Box>
|
||||
|
|
|
|||
520
src/bin/BinTransactions.tsx
Normal file
520
src/bin/BinTransactions.tsx
Normal file
|
|
@ -0,0 +1,520 @@
|
|||
import { CheckOutlined } from "@mui/icons-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 { 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 moment from "moment";
|
||||
import ObjectDescriber from "objects/ObjectDescriber";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { useBinAPI, useContractAPI, useFieldAPI, useGrainBagAPI, useSnackbar, useTransactionAPI } from "providers";
|
||||
import React from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import TransactionDataDisplay from "transactions/transactionDataDisplay";
|
||||
import { getGrainUnit } from "utils";
|
||||
|
||||
interface Props {
|
||||
bin: Bin
|
||||
permissions: pond.Permission[]
|
||||
refresh: () => void
|
||||
}
|
||||
|
||||
export default function BinTransactions(props: Props){
|
||||
const { bin, permissions, refresh } = props
|
||||
const [state, setState] = useState<pond.TransactionState>(pond.TransactionState.TRANSACTION_STATE_APPROVAL_REQUIRED)
|
||||
const transactionAPI = useTransactionAPI()
|
||||
const binAPI = useBinAPI()
|
||||
const grainBagAPI = useGrainBagAPI()
|
||||
const fieldAPI = useFieldAPI()
|
||||
const contractAPI = useContractAPI()
|
||||
const [transactions, setTransactions] = useState<Transaction[]>([])
|
||||
const [possibleTransactionMatches, setPossibleTransactionMatches] = useState<Transaction[]>([])
|
||||
const [tablePage, setTablePage] = useState(0)
|
||||
const [tablePageSize, setTablePageSize] = useState(10)
|
||||
const { openSnack } = useSnackbar();
|
||||
const [total, setTotal] = useState(0)
|
||||
const [grainMessage, setGrainMessage] = useState("")
|
||||
|
||||
//only need to do this for the bins since if a bin is picked for the secondary we need to check its inventory control type,
|
||||
//if it is also hybrid a matching transaction must be selected
|
||||
const [bins, setBins] = useState<Map<string, Bin>>(new Map())
|
||||
const [binOptions, setBinOptions] = useState<Option[]>([])
|
||||
|
||||
const [grainBagOptions, setGrainBagOptions] = useState<Option[]>([])
|
||||
|
||||
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>()
|
||||
const [requiresMatch, setRequiresMatch] = useState(false)
|
||||
|
||||
//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 => {
|
||||
let options: Option[] = []
|
||||
let binMap: Map<string, Bin> = new Map()
|
||||
resp.data.bins.map(b => {
|
||||
let bin = Bin.create(b)
|
||||
if(bin.key() !== props.bin.key()){
|
||||
options.push({label: bin.name(), value: bin.key(), group: bin.grainName()})
|
||||
binMap.set(bin.key(), bin)
|
||||
}
|
||||
})
|
||||
setBinOptions(options)
|
||||
setBins(binMap)
|
||||
})
|
||||
}
|
||||
|
||||
//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(), group: grainBag.grainName()}
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
//load fields
|
||||
const loadFields = useCallback(()=>{
|
||||
fieldAPI.listFields(0,0, "asc", "field_name").then(resp => {
|
||||
setFieldOptions(resp.data.fields.map(f => {
|
||||
let field = Field.create(f)
|
||||
return {label: field.name(), value: field.key(), group: field.grainName()}
|
||||
}))
|
||||
})
|
||||
},[])
|
||||
|
||||
//load contracts
|
||||
const loadContracts = useCallback(()=>{
|
||||
contractAPI.listContracts(0,0, "asc", "name").then(resp => {
|
||||
setContractOptions(resp.data.contracts.map(c => {
|
||||
let contract = Contract.create(c)
|
||||
return {label: contract.name(), value: contract.key(), group: contract.grainName()}
|
||||
}))
|
||||
})
|
||||
},[])
|
||||
|
||||
|
||||
|
||||
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(()=>{
|
||||
loadTransactionsFor()
|
||||
},[state, bin, loadTransactionsFor])
|
||||
|
||||
const closeDialog = () => {
|
||||
setPossibleTransactionMatches([])
|
||||
setselectedMatch(undefined)
|
||||
setRequiresMatch(false)
|
||||
setOpenDialog(false)
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
let transaction = cloneDeep(selectedTransaction)
|
||||
if(transaction){
|
||||
//use the dropdowns to update the from/to for the transaction, meaning if the user added another object it will adjust its inventory as well
|
||||
transaction.transaction.fromObject = fromType
|
||||
transaction.transaction.fromKey = fromOption?.value ?? ""
|
||||
transaction.transaction.toObject = toType
|
||||
transaction.transaction.toKey = toOption?.value ?? ""
|
||||
//need to do a check to make sure the transaction data contains a grain transaction
|
||||
if(transaction.transaction.transaction && transaction.transaction.transaction.grainTransaction) {
|
||||
transaction.transaction.transaction.grainTransaction.message = grainMessage
|
||||
}
|
||||
|
||||
//submit approval
|
||||
transactionAPI.submitApproval(transaction.transaction.key, true, transaction.transaction).then(resp => {
|
||||
//in the response if it succeeded remove (not reject but fully delete it) the matched transaction if one was selected
|
||||
if(selectedMatch){
|
||||
transactionAPI.removeTransaction(selectedMatch.transaction.key).then(resp => {
|
||||
openSnack("Updated and matched transactions")
|
||||
}).catch(err => {
|
||||
openSnack("Updated primary transaction but failed to remove matched transaction")
|
||||
})
|
||||
}else{
|
||||
openSnack("Updated Transaction")
|
||||
}
|
||||
}).catch(err => {
|
||||
openSnack("There was a problem submitting transaction approval")
|
||||
})
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
|
||||
const reject = () => {
|
||||
//reject the transaction
|
||||
if(selectedTransaction){
|
||||
transactionAPI.submitApproval(selectedTransaction.transaction.key, false, selectedTransaction.transaction).then(resp => {
|
||||
openSnack("Transaction rejected")
|
||||
}).catch(err => {
|
||||
openSnack("There was a problem rejecting the transaction")
|
||||
})
|
||||
}
|
||||
refresh()
|
||||
}
|
||||
|
||||
const getOptions = (type: pond.ObjectType) => {
|
||||
switch(type){
|
||||
case pond.ObjectType.OBJECT_TYPE_BIN:
|
||||
return binOptions
|
||||
case pond.ObjectType.OBJECT_TYPE_GRAIN_BAG:
|
||||
return grainBagOptions
|
||||
case pond.ObjectType.OBJECT_TYPE_FIELD:
|
||||
return fieldOptions
|
||||
case pond.ObjectType.OBJECT_TYPE_CONTRACT:
|
||||
return contractOptions
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// 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={closeDialog}>
|
||||
<DialogTitle>Accept Transaction</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box>
|
||||
<Typography>{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? weight + " mT" : bushels + " bushels"} of {grainTransaction.subtype} {grainDisplay} moved</Typography>
|
||||
<Typography margin={1} fontWeight={650}>From:</Typography>
|
||||
{/*Object type selection*/}
|
||||
<Autocomplete
|
||||
sx={{marginY: 1}}
|
||||
disabled={fromDisabled}
|
||||
id="autoFromTypes"
|
||||
value={fromType}
|
||||
options={[
|
||||
pond.ObjectType.OBJECT_TYPE_BIN,
|
||||
pond.ObjectType.OBJECT_TYPE_GRAIN_BAG,
|
||||
pond.ObjectType.OBJECT_TYPE_FIELD
|
||||
|
||||
]}
|
||||
getOptionLabel={(option: pond.ObjectType) => ObjectDescriber(option).name}
|
||||
fullWidth
|
||||
onChange={(_, 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){
|
||||
if(grainBagOptions.length === 0){
|
||||
loadGrainbags()
|
||||
}
|
||||
}else if (val === pond.ObjectType.OBJECT_TYPE_FIELD){
|
||||
if (fieldOptions.length === 0){
|
||||
loadFields()
|
||||
}
|
||||
}
|
||||
}}
|
||||
renderInput={params => <TextField {...params} label="Type" />}
|
||||
/>
|
||||
{/* object selection */}
|
||||
<Autocomplete
|
||||
sx={{marginY: 1}}
|
||||
disabled={fromDisabled}
|
||||
id="autoFromObjects"
|
||||
value={fromOption}
|
||||
options={getOptions(fromType)}
|
||||
getOptionLabel={(option: Option) => option.label}
|
||||
fullWidth
|
||||
onChange={(_, 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)
|
||||
})
|
||||
//if the options are bin types check if the selected bin uses hybrid controls
|
||||
if(fromType === pond.ObjectType.OBJECT_TYPE_BIN && val){
|
||||
let bin = bins.get(val.value)
|
||||
if(bin){
|
||||
setRequiresMatch(
|
||||
bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR ||
|
||||
bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_CABLE)
|
||||
}
|
||||
}
|
||||
}}
|
||||
renderInput={params => <TextField {...params} label="From" />}
|
||||
/>
|
||||
<Typography margin={1} fontWeight={650}>To:</Typography>
|
||||
<Autocomplete
|
||||
sx={{marginY: 1}}
|
||||
disabled={toDisabled}
|
||||
id="autoFromTypes"
|
||||
value={toType}
|
||||
options={[
|
||||
pond.ObjectType.OBJECT_TYPE_BIN,
|
||||
pond.ObjectType.OBJECT_TYPE_GRAIN_BAG,
|
||||
pond.ObjectType.OBJECT_TYPE_CONTRACT
|
||||
]}
|
||||
getOptionLabel={(option: pond.ObjectType) => ObjectDescriber(option).name}
|
||||
fullWidth
|
||||
onChange={(_, 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){
|
||||
if(grainBagOptions.length === 0){
|
||||
loadGrainbags()
|
||||
}
|
||||
}else if (val === pond.ObjectType.OBJECT_TYPE_CONTRACT){
|
||||
if(contractOptions.length === 0){
|
||||
loadContracts()
|
||||
}
|
||||
}
|
||||
}}
|
||||
renderInput={params => <TextField {...params} label="Type" />}
|
||||
/>
|
||||
<Autocomplete
|
||||
sx={{marginY: 1}}
|
||||
disabled={toDisabled}
|
||||
groupBy={option => {
|
||||
if (option.group) {
|
||||
return option.group;
|
||||
}
|
||||
return "No Grain Type";
|
||||
}}
|
||||
id="autoFromObjects"
|
||||
value={toOption}
|
||||
options={getOptions(toType)}
|
||||
getOptionLabel={(option: Option) => option.label}
|
||||
fullWidth
|
||||
onChange={(_, val) => {
|
||||
setToOption(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 to is the object selected and the from is unknown
|
||||
if(transaction.toKey === val?.value && transaction.fromKey === ""){
|
||||
matches.push(Transaction.create(transaction))
|
||||
}
|
||||
})
|
||||
setPossibleTransactionMatches(matches)
|
||||
})
|
||||
//if the options are bin types check if the selected bin uses hybrid controls
|
||||
if(fromType === pond.ObjectType.OBJECT_TYPE_BIN && val){
|
||||
let bin = bins.get(val.value)
|
||||
if(bin){
|
||||
setRequiresMatch(
|
||||
bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR ||
|
||||
bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_CABLE)
|
||||
}
|
||||
}
|
||||
}}
|
||||
renderInput={params => <TextField {...params} label="From" />}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<TextField
|
||||
label="Message(optional)"
|
||||
margin="dense"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={grainMessage}
|
||||
onChange={e => setGrainMessage(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
{toOption &&
|
||||
<Box paddingTop={1}>
|
||||
<Typography variant="h6">Possible matching Transactions</Typography>
|
||||
<Typography>{possibleTransactionMatches.length > 0 ? "Pick a transaction below if you want to match them together" : "No matching transactions found"}</Typography>
|
||||
{possibleTransactionMatches.map(t => {
|
||||
// get the display information for the transaction
|
||||
const grainTransaction = t.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
|
||||
return (
|
||||
<Card sx={{marginTop: 1, padding: 1}} key={t.transaction.key}>
|
||||
<Box display="flex" justifyContent="space-between" height={40}>
|
||||
<Typography sx={{fontWeight: 650, margin: "auto", marginLeft: 0}}>
|
||||
{moment(t.transaction.timestamp).fromNow()}
|
||||
</Typography>
|
||||
<Box>
|
||||
{selectedMatch?.transaction.key === t.transaction.key && <CheckOutlined htmlColor="green" />}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Typography sx={{margin: "auto", marginLeft: 0}}>{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? weight + " mT" : bushels + " bushels"} of {grainTransaction.subtype} {grainDisplay} moved</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={selectedMatch?.transaction.key === t.transaction.key}
|
||||
color="primary"
|
||||
onClick={()=>{
|
||||
setselectedMatch(t)
|
||||
}}>
|
||||
Set Match
|
||||
</Button>
|
||||
</Box>
|
||||
</Card>
|
||||
)
|
||||
})}
|
||||
</Box>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="contained" onClick={closeDialog}>Close</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() => {
|
||||
reject()
|
||||
closeDialog()
|
||||
}}>
|
||||
Reject
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={requiresMatch && selectedMatch === undefined}
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
submit()
|
||||
closeDialog()
|
||||
//re-load bin
|
||||
}}>
|
||||
Approve
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</ResponsiveDialog>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{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 */}
|
||||
<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={permissions.includes(pond.Permission.PERMISSION_WRITE) ? (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)
|
||||
} : undefined}
|
||||
rows={transactions}
|
||||
handleRowsPerPageChange={(e)=>{setTablePageSize(e.target.value)}}
|
||||
setPage={(page)=>{setTablePage(page)}}
|
||||
page={tablePage}
|
||||
pageSize={tablePageSize}
|
||||
total={total}
|
||||
/>
|
||||
</Card>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
|
@ -285,7 +285,9 @@ export default function BinGraphs(props: Props) {
|
|||
<Box>
|
||||
<BinWaterLevel
|
||||
bin={bin}
|
||||
range={{ start: startDate, end: endDate }}
|
||||
// range={{ start: startDate, end: endDate }}
|
||||
start={startDate.toISOString()}
|
||||
end={endDate.toISOString()}
|
||||
plenumKey={componentDevices.get(plenums[0].key()) + ":" + plenums[0].key()}
|
||||
cableKey={componentDevices.get(moistureCables[0].key()) + ":" + moistureCables[0].key()}
|
||||
pressureKey={componentDevices.get(pressures[0].key()) + ":" + pressures[0].key()}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import React, { useEffect, useState } from "react";
|
|||
|
||||
interface Props {
|
||||
bin: Bin;
|
||||
range: DateRange;
|
||||
// range: DateRange;
|
||||
start: string,
|
||||
end: string,
|
||||
plenumKey: string;
|
||||
cableKey: string;
|
||||
pressureKey: string;
|
||||
|
|
@ -23,7 +25,9 @@ interface Props {
|
|||
export default function BinWaterLevel(props: Props) {
|
||||
const {
|
||||
bin,
|
||||
range,
|
||||
//range,
|
||||
start,
|
||||
end,
|
||||
plenumKey,
|
||||
cableKey,
|
||||
pressureKey,
|
||||
|
|
@ -63,8 +67,8 @@ export default function BinWaterLevel(props: Props) {
|
|||
binAPI
|
||||
.listBinLiters(
|
||||
bin.key(),
|
||||
range.start.toISOString(),
|
||||
range.end.toISOString(),
|
||||
start,
|
||||
end,
|
||||
grainMoisture,
|
||||
//(note: the variable needs to be the device the component belongs to and then the component key ie. "10:LTe7Q1sT5g65")
|
||||
plenumKey,
|
||||
|
|
@ -94,7 +98,7 @@ export default function BinWaterLevel(props: Props) {
|
|||
});
|
||||
}
|
||||
}
|
||||
}, [binAPI, range, bin, grainMoisture, plenumKey, cableKey, pressureKey, as]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}, [binAPI, start, end, bin, grainMoisture, plenumKey, cableKey, pressureKey, as]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
|
|
|||
|
|
@ -184,16 +184,18 @@ export default function CableEstimator() {
|
|||
if(tableSearch !== ""){
|
||||
//filter the data in the cloned list
|
||||
clone = clone.filter(bin => {
|
||||
if(bin.Manufacturer.toLowerCase().includes(tableSearch.toLowerCase())) return true
|
||||
if(bin.Model.toLowerCase().includes(tableSearch.toLowerCase())) return true
|
||||
if(bin.Capacity.toString().toLowerCase().includes(tableSearch.toLowerCase())) return true
|
||||
if(bin.HopperAngle.toString().toLowerCase().includes(tableSearch.toLowerCase())) return true
|
||||
if(bin.Diameter.toString().toLowerCase().includes(tableSearch.toLowerCase())) return true
|
||||
if(bin.Sidewall.toString().toLowerCase().includes(tableSearch.toLowerCase())) return true
|
||||
if(bin.Peak.toString().toLowerCase().includes(tableSearch.toLowerCase())) return true
|
||||
if(bin.EaveToPeak.toString().toLowerCase().includes(tableSearch.toLowerCase())) return true
|
||||
if(bin.RoofAngle.toString().toLowerCase().includes(tableSearch.toLowerCase())) return true
|
||||
if(bin.Rings.toString().toLowerCase().includes(tableSearch.toLowerCase())) return true
|
||||
//doing a partial search so if any string contains the search string it will return true
|
||||
const lowerSearch = tableSearch.toLowerCase()
|
||||
if(bin.Manufacturer.toLocaleLowerCase().includes(lowerSearch)) return true
|
||||
if(bin.Model.toLowerCase().includes(lowerSearch)) return true
|
||||
if(bin.Capacity.toString().toLowerCase().includes(lowerSearch)) return true
|
||||
if(bin.HopperAngle.toString().toLowerCase().includes(lowerSearch)) return true
|
||||
if(bin.Diameter.toString().toLowerCase().includes(lowerSearch)) return true
|
||||
if(bin.Sidewall.toString().toLowerCase().includes(lowerSearch)) return true
|
||||
if(bin.Peak.toString().toLowerCase().includes(lowerSearch)) return true
|
||||
if(bin.EaveToPeak.toString().toLowerCase().includes(lowerSearch)) return true
|
||||
if(bin.RoofAngle.toString().toLowerCase().includes(lowerSearch)) return true
|
||||
if(bin.Rings.toString().toLowerCase().includes(lowerSearch)) return true
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import GrainDescriber from "grain/GrainDescriber";
|
|||
import { Contract } from "models/Contract";
|
||||
import moment from "moment";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { useGlobalState, useTaskAPI, useContractAPI } from "providers";
|
||||
import { useGlobalState, useTaskAPI, useContractAPI, useSnackbar } from "providers";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { getGrainUnit } from "utils";
|
||||
|
||||
|
|
@ -63,6 +63,7 @@ export default function ContractSettings(props: Props) {
|
|||
const [deleteWindow, setDeleteWindow] = useState(false);
|
||||
const [createDeliveryTask, setCreateDeliveryTask] = useState(false);
|
||||
const [conversionValue, setConversionValue] = useState("1");
|
||||
const { openSnack } = useSnackbar()
|
||||
const [labels, setLabels] = useState<InputLabels>({
|
||||
sizeLabel: "Size",
|
||||
commodityLabel: "Commodity",
|
||||
|
|
@ -174,11 +175,14 @@ export default function ContractSettings(props: Props) {
|
|||
contractAPI
|
||||
.updateContract(contract.key(), name, settings, as)
|
||||
.then(resp => {
|
||||
console.log("Updated Contract");
|
||||
//console.log("Updated Contract");
|
||||
openSnack("Contract Updated")
|
||||
close();
|
||||
})
|
||||
.catch(err => {
|
||||
console.log("Error updating contract");
|
||||
//console.log("Error updating contract");
|
||||
openSnack("Error Updating Contract")
|
||||
|
||||
});
|
||||
} else {
|
||||
let settings: pond.ContractSettings = pond.ContractSettings.create();
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ export class Bin {
|
|||
return GrainDescriber(this.grain()).name;
|
||||
}
|
||||
} else {
|
||||
return "";
|
||||
return "None";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -234,4 +234,16 @@ export class Contract {
|
|||
}
|
||||
return storedunitVal;
|
||||
}
|
||||
|
||||
public grainName(): string {
|
||||
if (this.grain() !== pond.Grain.GRAIN_INVALID) {
|
||||
if (this.grain() === pond.Grain.GRAIN_CUSTOM) {
|
||||
return this.customType();
|
||||
} else {
|
||||
return GrainDescriber(this.grain()).name;
|
||||
}
|
||||
} else {
|
||||
return "None";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { or } from "utils/types";
|
|||
import area from "@turf/area";
|
||||
import { Feature } from "geojson";
|
||||
import { GeometryMapping } from "./GeometryMapping";
|
||||
import GrainDescriber from "grain/GrainDescriber";
|
||||
|
||||
export class Field {
|
||||
public settings: pond.FieldSettings = pond.FieldSettings.create();
|
||||
|
|
@ -150,4 +151,16 @@ export class Field {
|
|||
}
|
||||
return coords;
|
||||
}
|
||||
|
||||
public grainName(): string {
|
||||
if (this.grain() !== pond.Grain.GRAIN_INVALID) {
|
||||
if (this.grain() === pond.Grain.GRAIN_CUSTOM) {
|
||||
return this.customType();
|
||||
} else {
|
||||
return GrainDescriber(this.grain()).name;
|
||||
}
|
||||
} else {
|
||||
return "None";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import GrainDescriber from "grain/GrainDescriber";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { or } from "utils/types";
|
||||
|
|
@ -132,4 +133,16 @@ export class GrainBag {
|
|||
}
|
||||
return bpt;
|
||||
}
|
||||
|
||||
public grainName(): string {
|
||||
if (this.grain() !== pond.Grain.GRAIN_INVALID) {
|
||||
if (this.grain() === pond.Grain.GRAIN_CUSTOM) {
|
||||
return this.customType();
|
||||
} else {
|
||||
return GrainDescriber(this.grain()).name;
|
||||
}
|
||||
} else {
|
||||
return "None";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,7 @@ const useStyles = makeStyles((theme: Theme) => ({
|
|||
},
|
||||
drawer: {
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
zIndex: 10
|
||||
zIndex: "10 !important"
|
||||
},
|
||||
drawerPaper: {
|
||||
width: drawerWidth
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ import { useNavigate, useParams } from "react-router-dom";
|
|||
import { Controller } from "models/Controller";
|
||||
import TaskViewer from "tasks/TaskViewer";
|
||||
import ButtonGroup from "common/ButtonGroup";
|
||||
import BinTransactions from "bin/BinTransactions";
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
|
|
@ -195,7 +196,7 @@ export default function Bin(props: Props) {
|
|||
const [heaters, setHeaters] = useState<Controller[]>([]);
|
||||
const [fans, setFans] = useState<Controller[]>([]);
|
||||
const [detail, setDetail] = useState<
|
||||
"inventory" | "sensors" | "analytics" | "presets" | "alerts"
|
||||
"inventory" | "sensors" | "analytics" | "presets" | "alerts" | "transactions"
|
||||
>("inventory");
|
||||
const [mobileTab, setMobileTab] = useState(0);
|
||||
const componentAPI = useComponentAPI();
|
||||
|
|
@ -886,6 +887,10 @@ export default function Bin(props: Props) {
|
|||
{
|
||||
title: "Presets",
|
||||
function: () => setDetail("presets")
|
||||
},
|
||||
{
|
||||
title: "Transactions",
|
||||
function: () => setDetail("transactions")
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
|
@ -963,6 +968,9 @@ export default function Bin(props: Props) {
|
|||
compositionNameMap={compositionNameMap}
|
||||
/>
|
||||
)}
|
||||
{detail === "transactions" &&
|
||||
<BinTransactions bin={bin} permissions={permissions} refresh={refresh}/>
|
||||
}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Card>
|
||||
|
|
@ -992,8 +1000,9 @@ export default function Bin(props: Props) {
|
|||
<Tab label="Inventory" style={{ fontWeight: mobileTab === 1 ? 650 : 500 }} />
|
||||
<Tab label="Sensors" style={{ fontWeight: mobileTab === 2 ? 650 : 500 }} />
|
||||
<Tab label="Analysis" style={{ fontWeight: mobileTab === 3 ? 650 : 500 }} />
|
||||
<Tab label="Alerts" style={{ fontWeight: mobileTab === 3 ? 650 : 500 }} />
|
||||
<Tab label="Presets" style={{ fontWeight: mobileTab === 3 ? 650 : 500 }} />
|
||||
<Tab label="Alerts" style={{ fontWeight: mobileTab === 4 ? 650 : 500 }} />
|
||||
<Tab label="Presets" style={{ fontWeight: mobileTab === 5 ? 650 : 500 }} />
|
||||
<Tab label="Transactions" style={{ fontWeight: mobileTab === 6 ? 650 : 500 }} />
|
||||
</Tabs>
|
||||
<TabPanelMine value={mobileTab} index={0}>
|
||||
{overview()}
|
||||
|
|
@ -1080,6 +1089,9 @@ export default function Bin(props: Props) {
|
|||
}}
|
||||
/>
|
||||
</TabPanelMine>
|
||||
<TabPanelMine value={mobileTab} index={6}>
|
||||
<BinTransactions bin={bin} permissions={permissions} refresh={refresh}/>
|
||||
</TabPanelMine>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ export default function Contracts() {
|
|||
as
|
||||
)
|
||||
.then(resp => {
|
||||
console.log(resp)
|
||||
let contracts: Contract[] = [];
|
||||
let contractPermissions: Map<string, pond.Permission[]> = new Map();
|
||||
resp.data.contracts.forEach(contract => {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { pond } from "protobuf-ts/pond";
|
|||
import { pondURL } from "./pond";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { useGlobalState } from "providers";
|
||||
import { or } from "utils";
|
||||
|
||||
export interface ITransactionAPIContext {
|
||||
addTransaction: (
|
||||
|
|
@ -25,12 +26,22 @@ export interface ITransactionAPIContext {
|
|||
toKey?: string,
|
||||
otherTeam?: string
|
||||
) => Promise<AxiosResponse<pond.ListTransactionsResponse>>;
|
||||
listTransactionsFor: (
|
||||
limit: number,
|
||||
offset: number,
|
||||
order: "asc" | "desc",
|
||||
objectKey: string,
|
||||
state?: pond.TransactionState,
|
||||
otherTeam?: string
|
||||
) => Promise<AxiosResponse<pond.ListTransactionsForResponse>>
|
||||
revokeTransaction: (key: string, otherTeam?: string) => Promise<AxiosResponse<pond.RevokeTransactionResponse>>;
|
||||
updateTransaction: (
|
||||
key: string,
|
||||
data: pond.TransactionData,
|
||||
otherTeam?: string
|
||||
) => Promise<AxiosResponse<pond.UpdateTransactionResponse>>;
|
||||
submitApproval: (key: string, approved: boolean, updatedData: pond.Transaction, otherTeam?: string) => Promise<AxiosResponse<pond.SubmitTransactionApprovalResponse>>;
|
||||
removeTransaction: (key: string, otherTeam?: string) => Promise<AxiosResponse<pond.RemoveTransactionResponse>>
|
||||
}
|
||||
|
||||
export const TransactionAPIContext = createContext<ITransactionAPIContext>(
|
||||
|
|
@ -41,7 +52,7 @@ interface Props {}
|
|||
|
||||
export default function TransactionProvider(props: PropsWithChildren<Props>) {
|
||||
const { children } = props;
|
||||
const { get, post, put } = useHTTP();
|
||||
const { get, post, put, del } = useHTTP();
|
||||
const [{ as }] = useGlobalState();
|
||||
|
||||
const addTransaction = (transaction: pond.Transaction, fileIDs?: string[], otherTeam?: string) => {
|
||||
|
|
@ -88,6 +99,35 @@ export default function TransactionProvider(props: PropsWithChildren<Props>) {
|
|||
);
|
||||
};
|
||||
|
||||
const listTransactionsFor = (
|
||||
limit: number,
|
||||
offset: number,
|
||||
order: "asc" | "desc",
|
||||
objectKey: string,
|
||||
state?: pond.TransactionState,
|
||||
otherTeam?: string
|
||||
) => {
|
||||
const view = otherTeam ? otherTeam : as
|
||||
const url = "/transactionsFor" +
|
||||
"?limit=" +
|
||||
limit +
|
||||
"&offset=" +
|
||||
offset +
|
||||
("&order=" + or(order, "asc")) +
|
||||
"&object=" + objectKey +
|
||||
(state ? "&state=" + state : "") +
|
||||
(view ? "&as=" + view : "")
|
||||
|
||||
return new Promise<AxiosResponse<pond.ListTransactionsForResponse>>((resolve, reject)=>{
|
||||
get<pond.ListTransactionsForResponse>(pondURL(url)).then(resp => {
|
||||
resp.data = pond.ListTransactionsForResponse.fromObject(resp.data)
|
||||
return resolve(resp)
|
||||
}).catch(err => {
|
||||
return reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const updateTransaction = (key: string, data: pond.TransactionData, otherTeam?: string) => {
|
||||
const view = otherTeam ? otherTeam : as
|
||||
if (view)
|
||||
|
|
@ -120,14 +160,35 @@ export default function TransactionProvider(props: PropsWithChildren<Props>) {
|
|||
return put<pond.TransactionPageDataResponse>(pondURL("/transactions/" + key + "/revoke"));
|
||||
};
|
||||
|
||||
const submitApproval = (key: string, approval: boolean, updatedData: pond.Transaction, otherTeam?: string) => {
|
||||
const view = otherTeam ? otherTeam : as
|
||||
let url = "/transactions/" + key + "/approval?approve=" + approval.toString()
|
||||
if(view){
|
||||
url = url + "&as=" + view
|
||||
}
|
||||
return post<pond.SubmitTransactionApprovalResponse>(pondURL(url), updatedData)
|
||||
}
|
||||
|
||||
const removeTransaction = (key: string, otherTeam?: string) => {
|
||||
const view = otherTeam ? otherTeam : as
|
||||
let url = "/transactions/" + key + "/remove"
|
||||
if(view){
|
||||
url = url + "&as=" + view
|
||||
}
|
||||
return del<pond.RemoveTransactionResponse>(pondURL(url))
|
||||
}
|
||||
|
||||
return (
|
||||
<TransactionAPIContext.Provider
|
||||
value={{
|
||||
addTransaction,
|
||||
listTransactions,
|
||||
listTransactionsFor,
|
||||
updateTransaction,
|
||||
transactionPageData,
|
||||
revokeTransaction
|
||||
revokeTransaction,
|
||||
submitApproval,
|
||||
removeTransaction
|
||||
}}>
|
||||
{children}
|
||||
</TransactionAPIContext.Provider>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue