M7-T06: add meter management UI (timeline, declare swap, edit) to Energy view

This commit is contained in:
2026-06-25 17:01:54 +02:00
parent 839faaf95b
commit 33f5de0591
6 changed files with 1244 additions and 0 deletions
+66
View File
@@ -214,6 +214,12 @@ export function useMetrics(uuid: string) {
// ===========================================================================
// Re-exported energy types for consumers
export type MeterResponse = components['schemas']['MeterResponse']
export type MeterListResponse = components['schemas']['MeterListResponse']
export type MeterDeclareRequest = components['schemas']['MeterDeclareRequest']
export type MeterPatchRequest = components['schemas']['MeterPatchRequest']
export type MeterReason = components['schemas']['MeterReason']
export type ContractResponse = components['schemas']['ContractResponse']
export type ContractDetailResponse = components['schemas']['ContractDetailResponse']
export type ContractVersionResponse = components['schemas']['ContractVersionResponse']
@@ -434,6 +440,66 @@ export function useRecomputeCosts() {
})
}
// ===========================================================================
// Meter hooks — typed TanStack Query wrappers for /api/energy/meters.
//
// Query-key conventions:
// ['energy-meters'] — meter list
// ===========================================================================
// ---------------------------------------------------------------------------
// Query: list all meter epochs
// ---------------------------------------------------------------------------
export function useMeters() {
return useQuery({
queryKey: ['energy-meters'],
queryFn: async () => {
const res = await apiClient.GET('/api/energy/meters')
return res.data
},
})
}
// ---------------------------------------------------------------------------
// Mutation: declare a new meter epoch (swap / home move / initial)
// ---------------------------------------------------------------------------
export function useDeclareMeter() {
const qc = useQueryClient()
return useMutation({
mutationFn: (body: MeterDeclareRequest) =>
apiClient.POST('/api/energy/meters', { body }),
onSuccess: () => {
void qc.invalidateQueries({ queryKey: ['energy-meters'] })
// Invalidate cost-related queries: a new meter may trigger recompute server-side.
void qc.invalidateQueries({ queryKey: ['energy-costs'] })
void qc.invalidateQueries({ queryKey: ['energy-costs-summary'] })
},
})
}
// ---------------------------------------------------------------------------
// Mutation: update (PATCH) a meter epoch (label / note / started_at)
// ---------------------------------------------------------------------------
export function useUpdateMeter() {
const qc = useQueryClient()
return useMutation({
mutationFn: ({ id, body }: { id: number; body: MeterPatchRequest }) =>
apiClient.PATCH('/api/energy/meters/{meter_id}', {
params: { path: { meter_id: id } },
body,
}),
onSuccess: () => {
void qc.invalidateQueries({ queryKey: ['energy-meters'] })
// Retroactive started_at correction triggers recompute server-side.
void qc.invalidateQueries({ queryKey: ['energy-costs'] })
void qc.invalidateQueries({ queryKey: ['energy-costs-summary'] })
},
})
}
// ---------------------------------------------------------------------------
// Query: time-range readings for a device (window + limit — never full-table)
// ---------------------------------------------------------------------------