adding the transaction page

This commit is contained in:
csawatzky 2025-04-08 11:43:39 -06:00
parent 975f3bba54
commit 97bbd41492
7 changed files with 371 additions and 15 deletions

View file

@ -50,7 +50,7 @@ import {
// import FanPicker from "fans/fanPicker"; // import FanPicker from "fans/fanPicker";
import { GrainOptions, ToGrainOption } from "grain"; import { GrainOptions, ToGrainOption } from "grain";
import GrainDescriber from "grain/GrainDescriber"; import GrainDescriber from "grain/GrainDescriber";
// import GrainTransaction from "grain/GrainTransaction"; import GrainTransaction from "grain/GrainTransaction";
import { GetGrainUseOptions, ToGrainUseOption } from "grain/GrainUse"; import { GetGrainUseOptions, ToGrainUseOption } from "grain/GrainUse";
import { useMobile, useSnackbar } from "hooks"; import { useMobile, useSnackbar } from "hooks";
import { clone, cloneDeep } from "lodash"; import { clone, cloneDeep } from "lodash";
@ -424,6 +424,7 @@ export default function BinSettings(props: Props) {
}; };
const updateBin = () => { const updateBin = () => {
console.log("bin settings update")
// If initial moisture is updated, so does the timestamp // If initial moisture is updated, so does the timestamp
if (form.inventory?.initialMoisture !== bin?.settings.inventory?.initialMoisture) { if (form.inventory?.initialMoisture !== bin?.settings.inventory?.initialMoisture) {
if (form.inventory) form.inventory.initialTimestamp = moment().format(); if (form.inventory) form.inventory.initialTimestamp = moment().format();
@ -450,7 +451,7 @@ export default function BinSettings(props: Props) {
form.lowTemp = lowTempC; form.lowTemp = lowTempC;
form.autoGrainNode = autoTopNode; form.autoGrainNode = autoTopNode;
if (form.inventory) form.inventory.inventoryControl = inventoryControl; if (form.inventory) form.inventory.inventoryControl = inventoryControl;
console.log("bin api update")
binAPI binAPI
.updateBin(bin.key(), form) .updateBin(bin.key(), form)
.then(response => { .then(response => {
@ -473,19 +474,18 @@ export default function BinSettings(props: Props) {
newBin.settings.inventory.grainBushels = bin.settings.inventory?.grainBushels ?? 0; //but also need to use the old inventory amount so the diff is applied correctly newBin.settings.inventory.grainBushels = bin.settings.inventory?.grainBushels ?? 0; //but also need to use the old inventory amount so the diff is applied correctly
} }
return ( return (
// <GrainTransaction <GrainTransaction
// open={grainUpdate} open={grainUpdate}
// mainObject={newBin} mainObject={newBin}
// grainAdjustment={grainDiff} grainAdjustment={grainDiff}
// close={() => { close={() => {
// setGrainUpdate(false); setGrainUpdate(false);
// setGrainDiff(0); setGrainDiff(0);
// }} }}
// callback={() => { callback={() => {
// close(true); close(true);
// }} }}
// /> />
null
); );
} }
}; };

View file

@ -203,6 +203,7 @@ export default function GrainTransaction(props: Props) {
//create a transaction to enter into the table //create a transaction to enter into the table
const createTransaction = (finalVal: number) => { const createTransaction = (finalVal: number) => {
console.log("create transaction")
let dest = selectedDestination; let dest = selectedDestination;
let source = selectedSource; let source = selectedSource;
let bpt = 1; let bpt = 1;
@ -248,13 +249,16 @@ export default function GrainTransaction(props: Props) {
grainTransaction.subtype = obj.subtype(); grainTransaction.subtype = obj.subtype();
} }
} }
console.log(newTransaction)
transactionAPI.addTransaction(newTransaction, imageIDs).then(resp => { transactionAPI.addTransaction(newTransaction, imageIDs).then(resp => {
console.log(resp)
newTransaction.key = resp.data.key; newTransaction.key = resp.data.key;
closeDialogs(true, newTransaction); closeDialogs(true, newTransaction);
}); });
}; };
const updateInventory = () => { const updateInventory = () => {
console.log("update inventory")
//this is the amount to add to the destination //this is the amount to add to the destination
let destinationAdd = Math.abs( let destinationAdd = Math.abs(
isNaN(parseFloat(grainChangeVal)) ? 1 : parseFloat(grainChangeVal) isNaN(parseFloat(grainChangeVal)) ? 1 : parseFloat(grainChangeVal)

19
src/models/Transaction.ts Normal file
View file

@ -0,0 +1,19 @@
import { cloneDeep } from "lodash";
import { pond } from "protobuf-ts/pond";
import { or } from "utils/types";
export class Transaction {
public transaction: pond.Transaction = pond.Transaction.create();
public static create(pb?: pond.Transaction): Transaction {
let my = new Transaction();
if (pb) {
my.transaction = pond.Transaction.fromObject(cloneDeep(or(pb, {})));
}
return my;
}
public static any(data: any): Transaction {
return Transaction.create(pond.Transaction.fromObject(cloneDeep(data)));
}
}

View file

@ -18,6 +18,7 @@ import Site from "pages/Site";
import Heaters from "pages/Heaters"; import Heaters from "pages/Heaters";
import Heater from "pages/Heater"; import Heater from "pages/Heater";
import Tasks from "pages/Tasks"; import Tasks from "pages/Tasks";
import Transactions from "pages/Transactions";
//import Site from "pages/Site"; //import Site from "pages/Site";
const DeviceHistory = lazy(() => import("pages/DeviceHistory")); const DeviceHistory = lazy(() => import("pages/DeviceHistory"));
@ -301,6 +302,7 @@ export default function Router(props: Props) {
{/* <Route index element={<Typography>Hello!</Typography>} /> */} {/* <Route index element={<Typography>Hello!</Typography>} /> */}
<Route path="users" element={<Users/>} /> <Route path="users" element={<Users/>} />
<Route path="tasks" element={<Tasks />} /> <Route path="tasks" element={<Tasks />} />
<Route path="transactions" element={<Transactions />} />
{/* Map pages */} {/* Map pages */}
<Route path="visualFarm" element={<FieldMap />} /> <Route path="visualFarm" element={<FieldMap />} />

165
src/pages/Transactions.tsx Normal file
View file

@ -0,0 +1,165 @@
import { useEffect, useState } from "react";
import PageContainer from "./PageContainer";
import { Box, Grid2 as Grid, TextField, Typography } from "@mui/material";
import { Moment } from "moment";
import { pond } from "protobuf-ts/pond";
import SearchSelect, { Option } from "common/SearchSelect";
import TransactionViewer from "transactions/transactionViewer";
import { TransactionOptions } from "objects/ObjectDescriber";
import { GetDefaultDateRange } from "common/time/DateRange";
import TimeBar from "common/time/TimeBar";
import { useTransactionAPI } from "providers";
import { Transaction } from "models/Transaction";
import { makeStyles } from "@mui/styles";
const useStyles = makeStyles(() => {
return ({
page: {
padding: 10
}
});
});
//this page will have the dropdown selectors to set the variables for query
export default function Transactions() {
const classes = useStyles();
const defaultDateRange = GetDefaultDateRange();
const [startDate, setStartDate] = useState<Moment>(defaultDateRange.start);
const [endDate, setEndDate] = useState<Moment>(defaultDateRange.end);
//these two variables are the object type
const [fromObject, setFromObject] = useState<pond.ObjectType | undefined>(undefined);
const [toObject, setToObject] = useState<pond.ObjectType | undefined>(undefined);
//these are simply for controlling what is shown in the select boxes
const [selectedFromObject, setSelectedFromObject] = useState<Option | null>();
const [selectedToObject, setSelectedToObject] = useState<Option | null>();
const transactionOptions = TransactionOptions();
const transactionAPI = useTransactionAPI();
const [isLoading, setIsLoading] = useState(false);
const [totalTransactions, setTotalTransactions] = useState<Transaction[]>([]);
const [filteredTransactions, setFilteredTransactions] = useState<Transaction[]>([]);
const [objectNames, setObjectNames] = useState<Map<string, string>>(new Map());
const [fromName, setFromName] = useState("");
const [toName, setToName] = useState("");
const updateDateRange = (newStartDate: any, newEndDate: any) => {
let range = GetDefaultDateRange();
range.start = newStartDate;
range.end = newEndDate;
setStartDate(newStartDate);
setEndDate(newEndDate);
};
//useEffect to filter the transactions to show based on the dropdown for object and text boxes for name entry
useEffect(() => {
let temp: Transaction[] = [];
totalTransactions.forEach(trans => {
if (
(!fromObject || trans.transaction.fromObject === fromObject) &&
(!toObject || trans.transaction.toObject === toObject) &&
(fromName === "" ||
objectNames
.get(trans.transaction.fromKey)
?.toLowerCase()
.includes(fromName.toLowerCase())) &&
(toName === "" ||
objectNames
.get(trans.transaction.toKey)
?.toLowerCase()
.includes(toName.toLowerCase()))
) {
temp.push(trans);
}
});
setFilteredTransactions(temp);
}, [fromObject, toObject, totalTransactions, fromName, toName, objectNames]);
//useEffect to get the transactions when the timeframe changes
useEffect(() => {
if (!isLoading) {
setIsLoading(true);
transactionAPI
.transactionPageData(startDate.toISOString(), endDate.toISOString())
.then(resp => {
setTotalTransactions(
resp.data.transactions.map((t: pond.Transaction) => Transaction.create(t))
);
let tempMap: Map<string, string> = new Map();
Object.keys(resp.data.nameMap).forEach(key => {
tempMap.set(key, resp.data.nameMap[key]);
});
setObjectNames(tempMap);
})
.catch(err => {
console.log("There was a problem loading your inventory transactions");
})
.finally(() => {
setIsLoading(false);
});
}
}, [transactionAPI, startDate, endDate]); // eslint-disable-line react-hooks/exhaustive-deps
return (
<PageContainer>
<Box className={classes.page}>
<TimeBar startDate={startDate} endDate={endDate} updateDateRange={updateDateRange} />
<Typography>Type</Typography>
<Grid container direction="row" spacing={2} style={{ margin: 10 }}>
<Grid>
<SearchSelect
style={{ width: 175 }}
label="From"
selected={selectedFromObject}
options={transactionOptions}
changeSelection={op => {
setSelectedFromObject(op);
setFromObject(op?.value);
}}
/>
</Grid>
<Grid>
<SearchSelect
style={{ width: 175 }}
label="To"
selected={selectedToObject}
options={transactionOptions}
changeSelection={op => {
setSelectedToObject(op);
setToObject(op?.value);
}}
/>
</Grid>
</Grid>
<Typography>Name</Typography>
<Grid container direction="row" spacing={2} style={{ margin: 10 }}>
<Grid>
<TextField
label="From"
variant="outlined"
style={{ width: 175 }}
value={fromName}
onChange={e => {
setFromName(e.target.value);
}}
/>
</Grid>
<Grid>
<TextField
label="To"
variant="outlined"
style={{ width: 175 }}
value={toName}
onChange={e => {
setToName(e.target.value);
}}
/>
</Grid>
</Grid>
<TransactionViewer
transactions={filteredTransactions}
loading={isLoading}
objectNames={objectNames}
/>
</Box>
</PageContainer>
);
}

View file

@ -0,0 +1,42 @@
import { Box, Typography } from "@mui/material";
import GrainDescriber from "grain/GrainDescriber";
import { Transaction } from "models/Transaction";
import { pond } from "protobuf-ts/pond";
import React from "react";
import { getGrainUnit } from "utils";
interface Props {
transaction: Transaction;
}
export default function TransactionDataDisplay(props: Props) {
const { transaction } = props;
const display = () => {
let transactionData = transaction.transaction.transaction;
if (transactionData) {
if (transactionData.grainTransaction) {
let gt = transactionData.grainTransaction;
return (
<Box>
<Typography>
Grain:{" "}
{gt.grainType === pond.Grain.GRAIN_CUSTOM
? gt.customGrain
: GrainDescriber(gt.grainType).name}
</Typography>
<Typography>Variant: {gt.subtype}</Typography>
<Typography>
{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && gt.bushelsPerTonne > 1
? "Weight: " + gt.bushels / gt.bushelsPerTonne + " mT"
: "Bushels: " + gt.bushels}
</Typography>
<Typography>Message: {gt.message}</Typography>
</Box>
);
}
}
};
return <React.Fragment>{display()}</React.Fragment>;
}

View file

@ -0,0 +1,124 @@
import { Box, Typography } from "@mui/material";
import ResponsiveTable, { Column } from "common/ResponsiveTable";
import { cloneDeep } from "lodash";
//import { getTableIcons } from "common/ResponsiveTable";
//import MaterialTable, { Column, MTableToolbar } from "material-table";
import { Transaction } from "models/Transaction";
import moment from "moment";
import ObjectDescriber from "objects/ObjectDescriber";
import React, { useEffect, useState } from "react";
import TransactionDataDisplay from "./transactionDataDisplay";
interface Props {
transactions: Transaction[];
loading?: boolean;
objectNames?: Map<string, string>;
}
export default function TransactionViewer(props: Props) {
const { transactions, loading, objectNames } = props;
const [displayedRows, setDisplayedRows] = useState<Transaction[]>([])
const [tablePage, setTablePage] = useState(0)
const [pageSize, setPageSize] = useState(10)
useEffect(()=>{
// let rows: Transaction[] = []
// transactions.forEach((transaction, index) => {
// if (index >= tablePage * pageSize && index < (tablePage + 1) * pageSize){
// rows.push(transaction)
// }
// })
let clone: Transaction[] = cloneDeep(transactions)
let rows = clone.splice(tablePage * pageSize, pageSize)
console.log(transactions)
console.log(rows)
setDisplayedRows(rows)
},[transactions, tablePage, pageSize])
const columns: Column<Transaction>[] = [
{
title: "Date",
render: row => {
return <Typography>{moment(row.transaction.timestamp).format("YYYY-MM-DD")}</Typography>;
}
},
{
title: "From",
render: row => {
return (
<React.Fragment>
<Typography>{ObjectDescriber(row.transaction.fromObject).name}</Typography>
<Typography>
{objectNames ? objectNames.get(row.transaction.fromKey) : row.transaction.fromKey}
</Typography>
</React.Fragment>
);
}
},
{
title: "To",
render: row => {
return (
<React.Fragment>
<Typography>{ObjectDescriber(row.transaction.toObject).name}</Typography>
<Typography>
{objectNames ? objectNames.get(row.transaction.toKey) : row.transaction.toKey}
</Typography>
</React.Fragment>
);
}
},
{
title: "Transaction",
render: row => {
return <TransactionDataDisplay transaction={row} />;
}
}
];
const myTable = () => {
return (
<ResponsiveTable<Transaction>
page={tablePage}
pageSize={pageSize}
rows={displayedRows}
total={transactions.length}
setPage={page => setTablePage(page)}
handleRowsPerPageChange={() => {}}
/>
)
// return (
// <MaterialTable
// isLoading={loading}
// key="devicesList"
// icons={getTableIcons()}
// columns={columns}
// data={transactions}
// options={{
// paging: true,
// pageSize: 10,
// pageSizeOptions: [5, 10, 20],
// padding: "dense",
// showTitle: true,
// toolbar: true,
// sorting: true,
// columnsButton: true,
// search: false
// }}
// localization={{
// body: { emptyDataSourceMessage: loading ? "" : "No Transactions found" }
// }}
// components={{
// Toolbar: props => (
// <React.Fragment>
// <MTableToolbar {...props} />
// </React.Fragment>
// )
// }}
// title="Inventory Transactions"
// />
// );
};
return <Box>{myTable()}</Box>;
}