M8-R13: simplify prices and meter controls
frontend / frontend (push) Successful in 53s
pytest / test (push) Successful in 4m34s
docker-image / build-and-push (push) Successful in 1m49s

This commit is contained in:
2026-08-27 20:01:41 +02:00
parent c9ce05d95a
commit b472f91f19
4 changed files with 145 additions and 34 deletions
+36 -7
View File
@@ -23,6 +23,7 @@ import {
Group,
Paper,
SegmentedControl,
ScrollArea,
} from '@mantine/core'
import {
LineChart,
@@ -60,6 +61,19 @@ function thermalUnit(section: string, key: string, currency: string): string {
return currency
}
function humanizeThermalField(value: string): string {
return value
.replace(/_/g, ' ')
.replace(/\b\w/g, (letter) => letter.toUpperCase())
}
function thermalSections(values: Record<string, Record<string, string>>): string[] {
const preferred = ['variable', 'standing']
return [...preferred.filter((section) => section in values), ...Object.keys(values)
.filter((section) => !preferred.includes(section))
.sort()]
}
// ---------------------------------------------------------------------------
// Time range helpers
// ---------------------------------------------------------------------------
@@ -399,13 +413,28 @@ export function TibberPrices() {
<Text fw={500}>Thermal contract snapshot</Text>
<Text size="sm">Version {data.contract_version_id ?? '—'} · effective {data.effective_from ?? '—'} to {data.effective_to ?? 'open'}</Text>
<Text size="sm" c="dimmed">This is a contract snapshot, not a 15-minute market spot price.</Text>
{Object.entries(data.values).map(([section, values]) => (
<Text size="sm" key={section} data-testid={`thermal-price-section-${section}`}>
{section}: {Object.entries(values).map(([key, value]) =>
`${key} ${value} ${thermalUnit(section, key, currency)}`,
).join(', ')}
</Text>
))}
<ScrollArea type="auto">
<Table withTableBorder withColumnBorders data-testid="thermal-price-table">
<Table.Thead>
<Table.Tr>
<Table.Th>Category</Table.Th>
<Table.Th>Charge</Table.Th>
<Table.Th>Rate</Table.Th>
<Table.Th>Unit</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{thermalSections(data.values!).flatMap((section) => Object.entries(data.values![section]).map(([key, rate]) => (
<Table.Tr key={`${section}-${key}`} data-testid={`thermal-price-row-${section}-${key}`}>
<Table.Td>{humanizeThermalField(section)}</Table.Td>
<Table.Td>{humanizeThermalField(key)}</Table.Td>
<Table.Td>{rate}</Table.Td>
<Table.Td>{thermalUnit(section, key, currency)}</Table.Td>
</Table.Tr>
)))}
</Table.Tbody>
</Table>
</ScrollArea>
</Stack>
</Paper>
)}