Cost
Cost Summary - composite reporting page (single-site). Reads cost-summary view model fields (from `useCostSummary`) and renders: - page header with "Cost Sum…
Cost Summary - composite reporting page (single-site).
Reads cost-summary view model fields (from useCostSummary) and renders: - page header with "Cost Summary" title and an optional action slot (setCostAction) pinned to the opposite edge - period selector slot (controls) - pass <TimeframeControls> for the OSS-style Year/Month picker or any other date selector element - shared CostContent 2x2 grid (charts + metric tiles)
Multi-site is intentionally out of scope for this extraction wave.
import { Cost } from "@tetherto/mdk-react-devkit";| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
metrics | Required | CostSummaryDisplayMetrics | null | — | — |
costLog | Required | readonly CostTimeSeriesEntry[] | — | — |
btcPriceLog | Required | readonly BtcPriceTimeSeriesEntry[] | — | — |
totals | Required | CostSummaryMonetaryTotals | null | — | — |
isLoading | Optional | boolean | undefined | — | — |
error | Optional | unknown | — | — |
dateRange | Required | FinancialDateRange | null | — | — |
avgAllInCostData | Optional | readonly AvgAllInCostDataPoint[] | undefined | — | Optional revenue/cost time-series for the Avg All-in Cost panel. |
controls | Required | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | — | Period selector element. Pass `<TimeframeControls>` for the OSS-style year/month picker. |
setCostAction | Optional | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | undefined | — | Optional "Set Monthly Cost" header action slot. A `ReactElement` slot (rather than an href string) so consumers can hand in router-aware components like `<Link>` or `<Button onClick={...} />` without triggering a full page reload. |
Usage
Composite financial reporting page for a single mining site. Renders a page header, a period selector slot, and a 2×2 grid of charts and metric tiles driven by the cost-summary view model.
Notes
- Use
buildCostSummaryViewModelto transform the raw API response into the props this component expects. avgAllInCostDatacomes from a separate endpoint (useAvgAllInPowerCostDatain the OSS app) — omit it to hide the Avg All-in Cost panel.- For a custom page layout (different header, navigation), mount
CostContentdirectly instead and supply your own chrome. - Multi-site aggregation is out of scope for this component; mount one
Costper site and compose them yourself.
Examples
import { AvgAllInCostChart } from '@tetherto/mdk-react-devkit'export const AvgAllInCostChartExample = () => ( <div className="mdk-example-row"> <AvgAllInCostChart dateRange={null} isLoading={false} /> </div>)import { CostCharts } from '@tetherto/mdk-react-devkit'export const CostChartsExample = () => ( <div className="mdk-example-row"> <CostCharts costLog={[]} btcPriceLog={[]} totals={null} dateRange={null} isLoading={false} /> </div>)import { CostContent } from '@tetherto/mdk-react-devkit'export const CostContentExample = () => ( <div className="mdk-example-row"> <CostContent metrics={null} costLog={[]} btcPriceLog={[]} totals={null} dateRange={null} isLoading={false} /> </div>)import { CostMetrics } from '@tetherto/mdk-react-devkit'export const CostMetricsExample = () => ( <div className="mdk-example-row"> <CostMetrics metrics={{ allIn: 42.5, energy: 38.0, operations: 4.5 } as never} /> </div>)/** * Runnable example for Cost. * All imports must come from "@tetherto/mdk-react-devkit/domain". */import { type AvgAllInCostDataPoint, buildCostSummaryViewModel, Cost, type CostSummaryLogEntry, type CostSummaryResponse, type FinancialDateRange, PERIOD, TimeframeControls, type TimeframeControlsOnRangeChange,} from '@tetherto/mdk-react-devkit/domain'import { endOfMonth } from 'date-fns/endOfMonth'import { startOfMonth } from 'date-fns/startOfMonth'import { useMemo, useState } from 'react'const buildInitialDateRange = (): FinancialDateRange => ({ start: startOfMonth(new Date('2025-01-01T00:00:00.000Z')).getTime(), end: endOfMonth(new Date('2025-12-31T00:00:00.000Z')).getTime(), period: PERIOD.MONTHLY,})const monthTs = (month: number): number => Date.UTC(2025, month - 1, 15, 12)const buildLogEntry = (input: { month: number consumptionMWh: number energyCostsUSD: number operationalCostsUSD: number btcPrice: number}): CostSummaryLogEntry => { const totalCostsUSD = input.energyCostsUSD + input.operationalCostsUSD return { ts: monthTs(input.month), consumptionMWh: input.consumptionMWh, energyCostsUSD: input.energyCostsUSD, operationalCostsUSD: input.operationalCostsUSD, totalCostsUSD, allInCostPerMWh: totalCostsUSD / input.consumptionMWh, energyCostPerMWh: input.energyCostsUSD / input.consumptionMWh, btcPrice: input.btcPrice, }}const MOCK_LOG: CostSummaryLogEntry[] = [ { month: 1, consumptionMWh: 1_180, energyCostsUSD: 14_200, operationalCostsUSD: 7_100, btcPrice: 64_500, }, { month: 2, consumptionMWh: 1_320, energyCostsUSD: 15_900, operationalCostsUSD: 7_950, btcPrice: 67_800, }, { month: 3, consumptionMWh: 1_410, energyCostsUSD: 17_400, operationalCostsUSD: 8_600, btcPrice: 71_200, }, { month: 4, consumptionMWh: 1_290, energyCostsUSD: 15_500, operationalCostsUSD: 7_650, btcPrice: 65_900, }, { month: 5, consumptionMWh: 1_360, energyCostsUSD: 16_800, operationalCostsUSD: 8_200, btcPrice: 68_400, }, { month: 6, consumptionMWh: 1_470, energyCostsUSD: 18_500, operationalCostsUSD: 9_100, btcPrice: 72_500, },].map(buildLogEntry)const MOCK_SUMMARY: CostSummaryResponse = { log: MOCK_LOG, summary: { totalEnergyCostsUSD: MOCK_LOG.reduce((s, e) => s + e.energyCostsUSD, 0), totalOperationalCostsUSD: MOCK_LOG.reduce((s, e) => s + e.operationalCostsUSD, 0), totalCostsUSD: MOCK_LOG.reduce((s, e) => s + e.totalCostsUSD, 0), totalConsumptionMWh: MOCK_LOG.reduce((s, e) => s + e.consumptionMWh, 0), avgAllInCostPerMWh: MOCK_LOG.reduce((s, e) => s + e.totalCostsUSD, 0) / MOCK_LOG.reduce((s, e) => s + e.consumptionMWh, 0), avgEnergyCostPerMWh: MOCK_LOG.reduce((s, e) => s + e.energyCostsUSD, 0) / MOCK_LOG.reduce((s, e) => s + e.consumptionMWh, 0), avgBtcPrice: MOCK_LOG.reduce((s, e) => s + e.btcPrice, 0) / MOCK_LOG.length, },}const MOCK_AVG_ALL_IN: AvgAllInCostDataPoint[] = MOCK_LOG.map((e) => ({ ts: e.ts, revenueUSDPerMWh: (e.allInCostPerMWh ?? 0) * 1.28, costUSDPerMWh: e.allInCostPerMWh ?? 0,}))export const CostExample = () => { const [dateRange, setDateRange] = useState<FinancialDateRange>(buildInitialDateRange) const viewModel = useMemo(() => buildCostSummaryViewModel({ data: MOCK_SUMMARY }), []) const handleRangeChange: TimeframeControlsOnRangeChange = (range, options) => { setDateRange({ start: range[0].getTime(), end: range[1].getTime(), period: (options.period ?? dateRange.period) as FinancialDateRange['period'], }) } return ( <Cost metrics={viewModel.metrics} costLog={viewModel.costLog} btcPriceLog={viewModel.btcPriceLog} totals={viewModel.totals} dateRange={dateRange} avgAllInCostData={MOCK_AVG_ALL_IN} controls={ <TimeframeControls isMonthSelectVisible isWeekSelectVisible={false} dateRange={{ start: dateRange.start, end: dateRange.end }} onRangeChange={handleRangeChange} /> } /> )}import { OperationsEnergyChart } from '@tetherto/mdk-react-devkit'export const OperationsEnergyChartExample = () => ( <div className="mdk-example-row"> <OperationsEnergyChart totals={null} isLoading={false} /> </div>)import { ProductionCostChart } from '@tetherto/mdk-react-devkit'export const ProductionCostChartExample = () => ( <div className="mdk-example-row"> <ProductionCostChart costLog={[]} btcPriceLog={[]} dateRange={null} isLoading={false} /> </div>)ActionsSidebar
Full-height side panel for the MiningOS voting/approval workflow. Three sections (only rendered when non-empty): - **Draft** — locally-staged actions not yet…
CostContent
Renders the data-driven portion of the Cost page in a 2x2 Mosaic grid: - (1,1) Production Cost / Price - (1,2) Avg All-in Cost - (2,1) Operations vs Energy C…