import { Fragment, useCallback, useEffect, useState } from 'react' import { AlertTriangle } from 'lucide-react' import { formatMoney, formatUnits, groupByCategory, formatBasis } from '../types' import type { Category, ConsumptionCostReport, RollupReport, MeterCostRow } from '../types' import * as api from '../api' function extrasFor(m: { ccl_cost_pence: number; rab_levy_cost_pence: number; metering_cost_pence: number; other_charges_cost_pence: number }): number { return m.ccl_cost_pence + m.rab_levy_cost_pence + m.metering_cost_pence + m.other_charges_cost_pence } // Sub-meters (parent_meter_id set) read a subset of their parent's own // reading (e.g. the water softener sits inline on the main supply) — their // row is still shown for visibility, but excluding them here avoids the // subtotal double-counting water/energy that's already in the parent's figure. function sumMeterCosts(rows: MeterCostRow[]) { return rows.filter(r => !r.parent_meter_id).reduce((acc, r) => ({ consumption: acc.consumption + (r.consumption || 0), usage_cost_pence: acc.usage_cost_pence + r.usage_cost_pence, standing_cost_pence: acc.standing_cost_pence + r.standing_cost_pence, extras_cost_pence: acc.extras_cost_pence + extrasFor(r), vat_pence: acc.vat_pence + r.vat_pence, total_pence: acc.total_pence + r.total_pence, }), { consumption: 0, usage_cost_pence: 0, standing_cost_pence: 0, extras_cost_pence: 0, vat_pence: 0, total_pence: 0 }) } function currentPeriod(): string { const now = new Date() return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}` } export default function Reports() { const [categories, setCategories] = useState([]) const [categoryId, setCategoryId] = useState('') const [period, setPeriod] = useState(currentPeriod()) const [report, setReport] = useState(null) const [rollup, setRollup] = useState(null) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) useEffect(() => { api.fetchCategories().then(setCategories).catch(() => {}) }, []) const load = useCallback(() => { setLoading(true) setError(null) Promise.all([ api.fetchConsumptionCostReport(period, categoryId || undefined), api.fetchRollupReport(period), ]).then(([r, ru]) => { setReport(r); setRollup(ru) }) .catch(err => setError(err.message)) .finally(() => setLoading(false)) }, [period, categoryId]) useEffect(() => { load() }, [load]) return (

Reports

setPeriod(e.target.value)} />
{error &&
{error}
} {loading || !report ? (
Loading…
) : ( <>
{formatMoney(report.totals.total_pence)}
Total cost
{formatMoney(report.totals.usage_cost_pence)}
Usage
{formatMoney(report.totals.standing_cost_pence)}
Standing
{formatMoney(extrasFor(report.totals))}
Extras
{formatMoney(report.totals.vat_pence)}
VAT
Consumption & cost by meter
{report.meters.length === 0 ? (
No meters to report on.
) : (
{groupByCategory(report.meters).map(group => { const subtotal = sumMeterCosts(group.rows) return ( {group.rows.map(m => ( ))} ) })}
MeterConsumption UsageStandingExtras VATTotalBasis
{m.meter_name} {m.parent_meter_id && ( submeter )} {m.rate_changed_mid_period && ( `${s.tariff_name}: ${s.seg_start} – ${s.seg_end}`).join(', ')}> rate changed )} {formatUnits(m.consumption, m.unit_label)} {formatMoney(m.usage_cost_pence)} {formatMoney(m.standing_cost_pence)} {formatMoney(extrasFor(m))} {formatMoney(m.vat_pence)} {formatMoney(m.total_pence)} {formatBasis(m.status, m.as_of_date)}
{group.category_name} subtotal {formatUnits(subtotal.consumption, group.unit_label)} {formatMoney(subtotal.usage_cost_pence)} {formatMoney(subtotal.standing_cost_pence)} {formatMoney(subtotal.extras_cost_pence)} {formatMoney(subtotal.vat_pence)} {formatMoney(subtotal.total_pence)}
Total {formatMoney(report.totals.usage_cost_pence)} {formatMoney(report.totals.standing_cost_pence)} {formatMoney(extrasFor(report.totals))} {formatMoney(report.totals.vat_pence)} {formatMoney(report.totals.total_pence)}
)}
Sub-metering rollup
{!rollup || rollup.rollups.length === 0 ? (
No parent/child meter relationships configured.
) : (
{rollup.rollups.map(r => ( ))}
Parent meterParent consumptionChildren sumChildren
{r.parent_meter_name} {formatUnits(r.parent_consumption, r.unit_label)} {formatUnits(r.child_sum, r.unit_label)} {r.children.map(c => c.meter_name).join(', ')} {r.anomaly && ( Children exceed parent )}
)} )}
) }