M8-T19: add scope-aware energy contract and cost UI

This commit is contained in:
2026-08-23 21:22:06 +02:00
parent 963e43e3e4
commit 5b9d60e80a
10 changed files with 532 additions and 51 deletions
+34 -22
View File
@@ -10,6 +10,7 @@
*/
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import {
Table,
Button,
@@ -24,9 +25,9 @@ import {
Modal,
Accordion,
Code,
SegmentedControl,
} from '@mantine/core'
import {
useContracts,
useUpdateContract,
type ContractResponse,
type ContractDetailResponse,
@@ -244,7 +245,14 @@ function ContractTable({
// ---------------------------------------------------------------------------
export function ContractManager() {
const contractsQuery = useContracts()
const [scope, setScope] = useState<'electricity' | 'thermal'>('electricity')
const contractsQuery = useQuery({
queryKey: ['energy-contracts', scope],
queryFn: async () => {
const res = await apiClient.GET('/api/energy/contracts', { params: { query: { scope } } })
return res.data
},
})
const updateMutation = useUpdateContract()
const [showCreateForm, setShowCreateForm] = useState(false)
@@ -252,6 +260,13 @@ export function ContractManager() {
const [historyContract, setHistoryContract] = useState<ContractResponse | null>(null)
const [activatingId, setActivatingId] = useState<number | null>(null)
function handleScopeChange(nextScope: 'electricity' | 'thermal') {
setScope(nextScope)
setShowCreateForm(false)
setAddVersionContract(null)
setHistoryContract(null)
}
async function handleActivate(id: number) {
setActivatingId(id)
try {
@@ -265,44 +280,40 @@ export function ContractManager() {
// Render states
// ---------------------------------------------------------------------------
if (contractsQuery.isLoading) {
return (
<Center py="xl" data-testid="contracts-loading">
<Loader />
</Center>
)
}
if (contractsQuery.isError || !contractsQuery.data) {
return (
<Alert color="red" data-testid="contracts-load-error">
Failed to load contracts. Please refresh.
</Alert>
)
}
const contracts = contractsQuery.data.items
const contracts = contractsQuery.data?.items ?? []
return (
<Stack gap="lg" data-testid="contract-manager">
<Group justify="space-between" align="center">
<Text fw={500}>Energy Contracts</Text>
<Group gap="sm">
<Text fw={500}>Energy Contracts</Text>
<SegmentedControl
value={scope}
onChange={(value) => handleScopeChange(value as 'electricity' | 'thermal')}
data={[{ label: 'Electricity', value: 'electricity' }, { label: 'Thermal', value: 'thermal' }]}
data-testid="contracts-scope-selector"
/>
</Group>
<Button onClick={() => setShowCreateForm(true)} data-testid="contract-new-button">
New Contract
</Button>
</Group>
<ContractTable
{contractsQuery.isLoading && <Center py="xl" data-testid="contracts-loading"><Loader /></Center>}
{contractsQuery.isError && <Alert color="red" data-testid="contracts-load-error">Failed to load contracts. Please refresh.</Alert>}
{!contractsQuery.isLoading && !contractsQuery.isError && <ContractTable
contracts={contracts}
onActivate={handleActivate}
onAddVersion={(c) => setAddVersionContract(c)}
onViewHistory={(c) => setHistoryContract(c)}
activatingId={activatingId}
/>
/>}
{/* Create new contract */}
{showCreateForm && (
<ContractForm
defaultKind={scope === 'thermal' ? 'district_heating' : undefined}
scope={scope}
onClose={() => setShowCreateForm(false)}
onSaved={() => setShowCreateForm(false)}
/>
@@ -313,6 +324,7 @@ export function ContractManager() {
<ContractForm
contractId={addVersionContract.id}
defaultKind={addVersionContract.kind}
scope={scope}
onClose={() => setAddVersionContract(null)}
onSaved={() => setAddVersionContract(null)}
/>