Enhance Dashboard Components with Additional Statistics and History Displays
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good

- Expanded FinanceOverview, InventoryOverview, and SalesOverview components to include new statistics and history displays for various object types, improving data visibility.
- Added support for tax record statistics, payment history, and invoice history in FinanceOverview.
- Introduced stock transfer, shipment, and part stock history statistics in InventoryOverview.
- Enhanced SalesOverview with order item statistics and shipment history, providing a more comprehensive view of sales data.
- Updated ModelHistoryDisplay to support dynamic history groups, improving flexibility in data representation.
- Refactored various model definitions to include new statistical measures, enhancing the overall data model structure.
This commit is contained in:
Tom Butcher 2026-08-30 14:56:46 +01:00
parent b347ec3637
commit eb61700248
15 changed files with 783 additions and 57 deletions

View File

@ -1,9 +1,22 @@
import DashboardOverviewPage from '../common/DashboardOverviewPage'
import StatsDisplay from '../common/StatsDisplay'
import ModelHistoryDisplay from '../common/ModelHistoryDisplay'
import ObjectTable from '../common/ObjectTable'
const collapseDefaults = {
invoiceStats: true,
paymentStats: true
paymentStats: true,
taxRecordStats: true,
invoiceCountHistory: true,
invoiceAmountHistory: true,
paymentCountHistory: true,
paymentAmountHistory: true,
taxRecordCountHistory: true,
taxRecordAmountHistory: true,
overdueInvoices: true,
partiallyPaidInvoices: true,
dueInvoices: true,
authorisedPayments: true
}
const sections = [
@ -18,6 +31,135 @@ const sections = [
title: 'Payment Statistics',
className: 'no-t-padding-collapse',
content: <StatsDisplay objectType='payment' />
},
{
key: 'taxRecordStats',
title: 'Tax Record Statistics',
className: 'no-t-padding-collapse',
content: <StatsDisplay objectType='taxRecord' />
},
{
key: 'invoiceHistoryRow',
type: 'row',
columns: [
[
{
key: 'invoiceCountHistory',
title: 'Invoice Count History',
content: (
<ModelHistoryDisplay objectType='invoice' historyGroup='count' />
)
}
],
[
{
key: 'invoiceAmountHistory',
title: 'Invoice Amount History',
content: (
<ModelHistoryDisplay objectType='invoice' historyGroup='amount' />
)
}
]
]
},
{
key: 'paymentHistoryRow',
type: 'row',
columns: [
[
{
key: 'paymentCountHistory',
title: 'Payment Count History',
content: (
<ModelHistoryDisplay objectType='payment' historyGroup='count' />
)
}
],
[
{
key: 'paymentAmountHistory',
title: 'Payment Amount History',
content: (
<ModelHistoryDisplay objectType='payment' historyGroup='amount' />
)
}
]
]
},
{
key: 'taxRecordHistoryRow',
type: 'row',
columns: [
[
{
key: 'taxRecordCountHistory',
title: 'Tax Record Count History',
content: (
<ModelHistoryDisplay objectType='taxRecord' historyGroup='count' />
)
}
],
[
{
key: 'taxRecordAmountHistory',
title: 'Tax Record Amount History',
content: (
<ModelHistoryDisplay objectType='taxRecord' historyGroup='amount' />
)
}
]
]
},
{
key: 'overdueAndPartiallyPaidInvoicesRow',
type: 'row',
columns: [
[
{
key: 'overdueInvoices',
title: 'Overdue Invoices',
content: (
<ObjectTable type='invoice' masterFilter={{ state: 'overdue' }} />
)
}
],
[
{
key: 'partiallyPaidInvoices',
title: 'Partially Paid Invoices',
content: (
<ObjectTable
type='invoice'
masterFilter={{ state: 'partiallyPaid' }}
/>
)
}
]
]
},
{
key: 'dueInvoicesAndAuthorisedPaymentsRow',
type: 'row',
columns: [
[
{
key: 'dueInvoices',
title: 'Due Invoices',
content: (
<ObjectTable type='invoice' masterFilter={{ state: 'sent' }} />
)
}
],
[
{
key: 'authorisedPayments',
title: 'Authorised Payments',
content: (
<ObjectTable type='payment' masterFilter={{ state: 'authorised' }} />
)
}
]
]
}
]

View File

@ -9,15 +9,28 @@ const collapseDefaults = {
productStockStats: true,
purchaseOrderStats: true,
stockEventStats: true,
partStockHistory: true,
stockTransferStats: true,
stockLocationStats: true,
shipmentStats: true,
partStockCountHistory: true,
partStockValueHistory: true,
newPartStocks: true,
filamentStockHistory: true,
filamentStockCountHistory: true,
filamentStockValueHistory: true,
unconsumedFilamentStocks: true,
productStockHistory: true,
productStockCountHistory: true,
productStockValueHistory: true,
draftProductStocks: true,
purchaseOrderHistory: true,
purchaseOrderCountHistory: true,
purchaseOrderAmountHistory: true,
draftPurchaseOrders: true,
stockEventHistory: true
stockEventHistory: true,
stockTransferHistory: true,
draftStockTransfers: true,
sentPurchaseOrders: true,
usedPartStocks: true,
plannedShipments: true,
shippedInboundShipments: true
}
const sections = [
@ -52,24 +65,82 @@ const sections = [
content: <StatsDisplay objectType='stockEvent' />
},
{
key: 'partAndFilamentHistoryRow',
key: 'stockTransferStats',
title: 'Stock Transfer Statistics',
className: 'no-t-padding-collapse',
content: <StatsDisplay objectType='stockTransfer' />
},
{
key: 'stockLocationStats',
title: 'Stock Location Statistics',
className: 'no-t-padding-collapse',
content: <StatsDisplay objectType='stockLocation' />
},
{
key: 'shipmentStats',
title: 'Shipment Statistics',
className: 'no-t-padding-collapse',
content: <StatsDisplay objectType='shipment' />
},
{
key: 'partStockHistoryRow',
type: 'row',
columns: [
[
{
key: 'partStockHistory',
title: 'Part Stock History',
key: 'partStockCountHistory',
title: 'Part Stock Count History',
content: (
<ModelHistoryDisplay objectType='partStock' chartType='line' />
<ModelHistoryDisplay
objectType='partStock'
historyGroup='count'
chartType='line'
/>
)
}
],
[
{
key: 'filamentStockHistory',
title: 'Filament Stock History',
key: 'partStockValueHistory',
title: 'Part Stock Quantity History',
content: (
<ModelHistoryDisplay objectType='filamentStock' chartType='line' />
<ModelHistoryDisplay
objectType='partStock'
historyGroup='value'
chartType='line'
/>
)
}
]
]
},
{
key: 'filamentStockHistoryRow',
type: 'row',
columns: [
[
{
key: 'filamentStockCountHistory',
title: 'Filament Stock Count History',
content: (
<ModelHistoryDisplay
objectType='filamentStock'
historyGroup='count'
chartType='line'
/>
)
}
],
[
{
key: 'filamentStockValueHistory',
title: 'Filament Stock Weight History',
content: (
<ModelHistoryDisplay
objectType='filamentStock'
historyGroup='value'
chartType='line'
/>
)
}
]
@ -103,23 +174,63 @@ const sections = [
]
},
{
key: 'productAndPurchaseOrderHistoryRow',
key: 'productStockHistoryRow',
type: 'row',
columns: [
[
{
key: 'productStockHistory',
title: 'Product Stock History',
key: 'productStockCountHistory',
title: 'Product Stock Count History',
content: (
<ModelHistoryDisplay objectType='productStock' chartType='line' />
<ModelHistoryDisplay
objectType='productStock'
historyGroup='count'
chartType='line'
/>
)
}
],
[
{
key: 'purchaseOrderHistory',
title: 'Purchase Order History',
content: <ModelHistoryDisplay objectType='purchaseOrder' />
key: 'productStockValueHistory',
title: 'Product Stock Quantity History',
content: (
<ModelHistoryDisplay
objectType='productStock'
historyGroup='value'
chartType='line'
/>
)
}
]
]
},
{
key: 'purchaseOrderHistoryRow',
type: 'row',
columns: [
[
{
key: 'purchaseOrderCountHistory',
title: 'Purchase Order Count History',
content: (
<ModelHistoryDisplay
objectType='purchaseOrder'
historyGroup='count'
/>
)
}
],
[
{
key: 'purchaseOrderAmountHistory',
title: 'Purchase Order Amount History',
content: (
<ModelHistoryDisplay
objectType='purchaseOrder'
historyGroup='amount'
/>
)
}
]
]
@ -155,7 +266,7 @@ const sections = [
]
},
{
key: 'stockEventHistoryRow',
key: 'stockEventAndTransferHistoryRow',
type: 'row',
columns: [
[
@ -166,6 +277,85 @@ const sections = [
<ModelHistoryDisplay objectType='stockEvent' chartType='line' />
)
}
],
[
{
key: 'stockTransferHistory',
title: 'Stock Transfer History',
content: <ModelHistoryDisplay objectType='stockTransfer' />
}
]
]
},
{
key: 'draftTransfersAndSentPurchaseOrdersRow',
type: 'row',
columns: [
[
{
key: 'draftStockTransfers',
title: 'Draft Stock Transfers',
content: (
<ObjectTable
type='stockTransfer'
masterFilter={{ state: 'draft' }}
/>
)
}
],
[
{
key: 'sentPurchaseOrders',
title: 'Sent Purchase Orders',
content: (
<ObjectTable
type='purchaseOrder'
masterFilter={{ state: 'sent' }}
/>
)
}
]
]
},
{
key: 'usedPartsAndPlannedShipmentsRow',
type: 'row',
columns: [
[
{
key: 'usedPartStocks',
title: 'Used Part Stocks',
content: (
<ObjectTable type='partStock' masterFilter={{ state: 'used' }} />
)
}
],
[
{
key: 'plannedShipments',
title: 'Planned Shipments',
content: (
<ObjectTable type='shipment' masterFilter={{ state: 'planned' }} />
)
}
]
]
},
{
key: 'shippedInboundShipmentsRow',
type: 'row',
columns: [
[
{
key: 'shippedInboundShipments',
title: 'Inbound Shipments In Transit',
content: (
<ObjectTable
type='shipment'
masterFilter={{ orderType: 'purchaseOrder', state: 'shipped' }}
/>
)
}
]
]
}

View File

@ -5,17 +5,28 @@ import ObjectTable from '../common/ObjectTable'
const collapseDefaults = {
salesOrderStats: true,
orderItemStats: true,
listingStats: true,
marketplaceStats: true,
clientStats: true,
salesOrderHistory: true,
confirmedSalesOrders: true,
shipmentStats: true,
salesOrderCountHistory: true,
salesOrderAmountHistory: true,
listingHistory: true,
orderItemCountHistory: true,
orderItemAmountHistory: true,
shipmentCountHistory: true,
shipmentAmountHistory: true,
confirmedSalesOrders: true,
draftListings: true,
marketplaceHistory: true,
readyMarketplaces: true,
clientHistory: true,
activeClients: true
readyMarketplaces: true,
activeClients: true,
sentSalesOrders: true,
activeListings: true,
ordersToShip: true,
plannedOutboundShipments: true
}
const sections = [
@ -25,6 +36,12 @@ const sections = [
className: 'no-t-padding-collapse',
content: <StatsDisplay objectType='salesOrder' />
},
{
key: 'orderItemStats',
title: 'Order Item Statistics',
className: 'no-t-padding-collapse',
content: <StatsDisplay objectType='orderItem' />
},
{
key: 'listingStats',
title: 'Listing Statistics',
@ -44,16 +61,42 @@ const sections = [
content: <StatsDisplay objectType='client' />
},
{
key: 'salesOrderAndListingHistoryRow',
key: 'shipmentStats',
title: 'Shipment Statistics',
className: 'no-t-padding-collapse',
content: <StatsDisplay objectType='shipment' />
},
{
key: 'salesOrderHistoryRow',
type: 'row',
columns: [
[
{
key: 'salesOrderHistory',
title: 'Sales Order History',
content: <ModelHistoryDisplay objectType='salesOrder' />
key: 'salesOrderCountHistory',
title: 'Sales Order Count History',
content: (
<ModelHistoryDisplay objectType='salesOrder' historyGroup='count' />
)
}
],
[
{
key: 'salesOrderAmountHistory',
title: 'Sales Order Amount History',
content: (
<ModelHistoryDisplay
objectType='salesOrder'
historyGroup='amount'
/>
)
}
]
]
},
{
key: 'listingHistoryRow',
type: 'row',
columns: [
[
{
key: 'listingHistory',
@ -63,6 +106,54 @@ const sections = [
]
]
},
{
key: 'orderItemHistoryRow',
type: 'row',
columns: [
[
{
key: 'orderItemCountHistory',
title: 'Order Item Count History',
content: (
<ModelHistoryDisplay objectType='orderItem' historyGroup='count' />
)
}
],
[
{
key: 'orderItemAmountHistory',
title: 'Order Item Amount History',
content: (
<ModelHistoryDisplay objectType='orderItem' historyGroup='amount' />
)
}
]
]
},
{
key: 'shipmentHistoryRow',
type: 'row',
columns: [
[
{
key: 'shipmentCountHistory',
title: 'Shipment Count History',
content: (
<ModelHistoryDisplay objectType='shipment' historyGroup='count' />
)
}
],
[
{
key: 'shipmentAmountHistory',
title: 'Shipment Amount History',
content: (
<ModelHistoryDisplay objectType='shipment' historyGroup='amount' />
)
}
]
]
},
{
key: 'confirmedSalesOrdersAndDraftListingsRow',
type: 'row',
@ -90,6 +181,30 @@ const sections = [
]
]
},
{
key: 'sentSalesOrdersAndActiveListingsRow',
type: 'row',
columns: [
[
{
key: 'sentSalesOrders',
title: 'Sent Sales Orders',
content: (
<ObjectTable type='salesOrder' masterFilter={{ state: 'sent' }} />
)
}
],
[
{
key: 'activeListings',
title: 'Active Listings',
content: (
<ObjectTable type='listing' masterFilter={{ state: 'active' }} />
)
}
]
]
},
{
key: 'marketplaceAndClientHistoryRow',
type: 'row',
@ -131,6 +246,36 @@ const sections = [
}
]
]
},
{
key: 'ordersToShipAndPlannedShipmentsRow',
type: 'row',
columns: [
[
{
key: 'ordersToShip',
title: 'Orders To Ship',
content: (
<ObjectTable
type='salesOrder'
masterFilter={{ state: 'partiallyShipped' }}
/>
)
}
],
[
{
key: 'plannedOutboundShipments',
title: 'Planned Outbound Shipments',
content: (
<ObjectTable
type='shipment'
masterFilter={{ orderType: 'salesOrder', state: 'planned' }}
/>
)
}
]
]
}
]

View File

@ -35,14 +35,52 @@ const legendMeasureStyle = {
const toCssHeight = (value) =>
typeof value === 'number' ? `${value}px` : value
const isAmountStat = (statDef) => {
if (statDef.prefix === '£') return true
const name = statDef.name || ''
if (name.includes('Amount') || name.includes('Value')) return true
const combineList = statDef.combine || statDef.sum
if (
combineList?.some(
(statName) => statName.includes('Amount') || statName.endsWith('.sum')
)
) {
return true
}
return false
}
const isValueStat = (statDef) => {
if (isAmountStat(statDef)) return false
if (statDef.name?.endsWith('.sum')) return true
if (statDef.suffix) return true
return false
}
export const resolveStatHistoryGroup = (statDef) => {
if (statDef.history === false) return null
if (statDef.historyGroup) return statDef.historyGroup
if (isAmountStat(statDef)) return 'amount'
if (isValueStat(statDef)) return 'value'
return 'count'
}
export const getModelHistoryStats = (model, historyGroup = 'count') => {
return (model?.stats || []).filter(
(statDef) => resolveStatHistoryGroup(statDef) === historyGroup
)
}
const ModelHistoryDisplay = ({
objectType,
historyGroup = 'count',
startDate,
endDate,
styles,
height = 400,
chartType = 'bar'
chartType: chartTypeProp
}) => {
const chartType = chartTypeProp ?? (historyGroup === 'amount' ? 'line' : 'bar')
const { getModelHistory, connected } = useContext(ApiServerContext)
const { token } = useContext(AuthContext)
const [historyData, setHistoryData] = useState([])
@ -132,8 +170,8 @@ const ModelHistoryDisplay = ({
)
const modelStats = useMemo(
() => (model?.stats || []).filter((statDef) => statDef.history !== false),
[model]
() => getModelHistoryStats(model, historyGroup),
[model, historyGroup]
)
const themeColors = getColors()
@ -516,6 +554,7 @@ const ModelHistoryDisplay = ({
ModelHistoryDisplay.propTypes = {
objectType: PropTypes.string.isRequired,
historyGroup: PropTypes.oneOf(['count', 'amount', 'value']),
startDate: PropTypes.oneOfType([
PropTypes.string,
PropTypes.instanceOf(Date)

View File

@ -192,22 +192,19 @@ export const FilamentStock = {
name: 'unconsumed.count',
label: 'Unconsumed',
type: 'number',
color: 'success',
history: false
color: 'success'
},
{
name: 'used.count',
label: 'Used',
type: 'number',
color: 'warning',
history: false
color: 'warning'
},
{
name: 'consumed.count',
label: 'Consumed',
type: 'number',
color: 'default',
history: false
color: 'default'
},
{
name: 'totalCurrentWeight.sum',
@ -215,7 +212,8 @@ export const FilamentStock = {
type: 'number',
roundNumber: 2,
suffix: 'g',
cardWidth: 200
cardWidth: 200,
historyGroup: 'value'
}
]
}

View File

@ -741,6 +741,36 @@ export const Invoice = {
prefix: '£',
roundNumber: 2,
color: 'error'
},
{
name: 'partiallyPaid.partiallyPaidGrandTotalAmount.sum',
label: 'Partially Paid Amount',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'processing',
cardWidth: 200
},
{
name: 'paid.paidCount.count',
label: 'Paid',
type: 'number',
color: 'success'
},
{
name: 'paid.paidGrandTotalAmount.sum',
label: 'Paid Amount',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'success',
cardWidth: 200
},
{
name: 'cancelled.cancelledCount.count',
label: 'Cancelled',
type: 'number',
color: 'default'
}
]
}

View File

@ -503,5 +503,49 @@ export const OrderItem = {
readOnly: true,
columnWidth: 225
}
],
stats: [
{
name: 'draft.count',
label: 'Draft',
type: 'number',
color: 'default'
},
{
name: 'ordered.count',
label: 'Ordered',
type: 'number',
color: 'cyan'
},
{
name: 'shipped.count',
label: 'Shipped',
type: 'number',
color: 'processing'
},
{
name: 'received.count',
label: 'Received',
type: 'number',
color: 'success'
},
{
name: 'shippedValue.totalAmountWithTax.sum',
label: 'Shipped Value',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'processing',
cardWidth: 175
},
{
name: 'receivedValue.totalAmountWithTax.sum',
label: 'Received Value',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'success',
cardWidth: 175
}
]
}

View File

@ -257,36 +257,33 @@ export const PartStock = {
name: 'draft.count',
label: 'Draft',
type: 'number',
color: 'default',
history: false
color: 'default'
},
{
name: 'new.count',
label: 'New',
type: 'number',
color: 'success',
history: false
color: 'success'
},
{
name: 'used.count',
label: 'Used',
type: 'number',
color: 'warning',
history: false
color: 'warning'
},
{
name: 'consumed.count',
label: 'Consumed',
type: 'number',
color: 'default',
history: false
color: 'default'
},
{
name: 'totalCurrentQuantity.sum',
label: 'Total Current Quantity',
type: 'number',
roundNumber: 2,
cardWidth: 200
cardWidth: 200,
historyGroup: 'value'
}
]
}

View File

@ -379,6 +379,21 @@ export const Payment = {
prefix: '£',
roundNumber: 2,
color: 'red'
},
{
name: 'cancelled.cancelledCount.count',
label: 'Cancelled',
type: 'number',
color: 'default'
},
{
name: 'cancelled.cancelledAmount.sum',
label: 'Cancelled Amount',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'default',
cardWidth: 200
}
]
}

View File

@ -420,36 +420,33 @@ export const ProductStock = {
name: 'draft.count',
label: 'Draft',
type: 'number',
color: 'default',
history: false
color: 'default'
},
{
name: 'new.count',
label: 'New',
type: 'number',
color: 'success',
history: false
color: 'success'
},
{
name: 'used.count',
label: 'Used',
type: 'number',
color: 'warning',
history: false
color: 'warning'
},
{
name: 'consumed.count',
label: 'Consumed',
type: 'number',
color: 'default',
history: false
color: 'default'
},
{
name: 'totalCurrentQuantity.sum',
label: 'Total Current Quantity',
type: 'number',
roundNumber: 2,
cardWidth: 200
cardWidth: 200,
historyGroup: 'value'
}
]
}

View File

@ -449,6 +449,24 @@ export const PurchaseOrder = {
label: 'Completed',
type: 'number',
color: 'success'
},
{
name: 'awaitingReceiptValue.grandTotalAmount.sum',
label: 'Awaiting Receipt Value',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'warning',
cardWidth: 220
},
{
name: 'receivedValue.grandTotalAmount.sum',
label: 'Received Value',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'success',
cardWidth: 200
}
]
}

View File

@ -448,6 +448,24 @@ export const SalesOrder = {
label: 'Cancelled',
type: 'number',
color: 'error'
},
{
name: 'pipelineValue.grandTotalAmount.sum',
label: 'Pipeline Value',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'purple',
cardWidth: 200
},
{
name: 'completedValue.grandTotalAmount.sum',
label: 'Completed Value',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'success',
cardWidth: 200
}
]
}

View File

@ -428,5 +428,46 @@ export const Shipment = {
step: 0.01,
columnWidth: 275
}
],
stats: [
{
name: 'draft.count',
label: 'Draft',
type: 'number',
color: 'default'
},
{
name: 'planned.count',
label: 'Planned',
type: 'number',
color: 'warning'
},
{
name: 'shipped.count',
label: 'Shipped',
type: 'number',
color: 'processing'
},
{
name: 'delivered.count',
label: 'Delivered',
type: 'number',
color: 'success'
},
{
name: 'cancelled.count',
label: 'Cancelled',
type: 'number',
color: 'error'
},
{
name: 'inTransitValue.amountWithTax.sum',
label: 'In Transit Value',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'processing',
cardWidth: 200
}
]
}

View File

@ -115,6 +115,14 @@ export const StockEvent = {
type: 'number',
color: 'success',
cardWidth: 220
},
{
name: 'totalEvents',
label: 'Total Events',
type: 'number',
color: 'default',
cardWidth: 175,
sum: ['partStock.count', 'filamentStock.count', 'productStock.count']
}
]
}

View File

@ -224,5 +224,49 @@ export const TaxRecord = {
type: 'dateTime',
readOnly: true
}
],
stats: [
{
name: 'total.count.count',
label: 'Total Records',
type: 'number',
color: 'default'
},
{
name: 'total.taxAmount.sum',
label: 'Total Tax',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'warning',
cardWidth: 175
},
{
name: 'total.amount.sum',
label: 'Total Transaction Value',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'default',
cardWidth: 220
},
{
name: 'salesOrder.taxAmount.sum',
label: 'Sales Tax',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'success',
cardWidth: 175
},
{
name: 'purchaseOrder.taxAmount.sum',
label: 'Purchase Tax',
type: 'number',
prefix: '£',
roundNumber: 2,
color: 'processing',
cardWidth: 175
}
]
}