nova stranka flow a obsluha
This commit is contained in:
101
frontend/src/components/EnergyFlowSankey.tsx
Normal file
101
frontend/src/components/EnergyFlowSankey.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import { ResponsiveSankey } from '@nivo/sankey'
|
||||
|
||||
export type FlowTotals = {
|
||||
pv_to_load_kwh: number
|
||||
pv_to_batt_kwh: number
|
||||
pv_to_grid_kwh: number
|
||||
batt_to_load_kwh: number
|
||||
batt_to_grid_kwh: number
|
||||
grid_to_load_kwh: number
|
||||
grid_to_batt_kwh: number
|
||||
}
|
||||
|
||||
const NODES = [
|
||||
{ id: 'FVE' },
|
||||
{ id: 'Síť' },
|
||||
{ id: 'Baterie' },
|
||||
{ id: 'Spotřeba' },
|
||||
] as const
|
||||
|
||||
function buildLinks(t: FlowTotals): { source: string; target: string; value: number }[] {
|
||||
const out: { source: string; target: string; value: number }[] = []
|
||||
const add = (source: string, target: string, v: number) => {
|
||||
if (v > 0.0005) out.push({ source, target, value: v })
|
||||
}
|
||||
add('FVE', 'Spotřeba', t.pv_to_load_kwh)
|
||||
add('FVE', 'Baterie', t.pv_to_batt_kwh)
|
||||
add('FVE', 'Síť', t.pv_to_grid_kwh)
|
||||
add('Baterie', 'Spotřeba', t.batt_to_load_kwh)
|
||||
add('Baterie', 'Síť', t.batt_to_grid_kwh)
|
||||
add('Síť', 'Spotřeba', t.grid_to_load_kwh)
|
||||
add('Síť', 'Baterie', t.grid_to_batt_kwh)
|
||||
return out
|
||||
}
|
||||
|
||||
type Props = {
|
||||
totals: FlowTotals | null
|
||||
}
|
||||
|
||||
export function EnergyFlowSankey({ totals }: Props) {
|
||||
if (!totals) {
|
||||
return (
|
||||
<div className="flex h-[440px] items-center justify-center text-sm text-slate-500">
|
||||
Žádná data
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const links = buildLinks(totals)
|
||||
if (links.length === 0) {
|
||||
return (
|
||||
<div className="flex h-[440px] items-center justify-center text-sm text-slate-500">
|
||||
V tomto měsíci nejsou žádné modelované toky (chybí audit / telemetrie).
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-[440px] w-full min-h-[320px]">
|
||||
<ResponsiveSankey
|
||||
data={{ nodes: [...NODES], links }}
|
||||
margin={{ top: 24, right: 180, bottom: 24, left: 24 }}
|
||||
align="justify"
|
||||
sort="input"
|
||||
colors={{ scheme: 'set2' }}
|
||||
nodeOpacity={1}
|
||||
nodeHoverOpacity={1}
|
||||
nodeThickness={20}
|
||||
nodeSpacing={28}
|
||||
nodeBorderWidth={0}
|
||||
linkOpacity={0.45}
|
||||
linkHoverOpacity={0.75}
|
||||
linkContract={2}
|
||||
enableLinkGradient
|
||||
labelPosition="outside"
|
||||
labelOrientation="horizontal"
|
||||
labelPadding={12}
|
||||
labelTextColor={{ from: 'color', modifiers: [['darker', 1.2]] }}
|
||||
theme={{
|
||||
background: 'transparent',
|
||||
labels: {
|
||||
text: {
|
||||
fill: '#e2e8f0',
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
container: {
|
||||
background: '#1e293b',
|
||||
color: '#f8fafc',
|
||||
fontSize: 12,
|
||||
borderRadius: 8,
|
||||
border: '1px solid #334155',
|
||||
},
|
||||
},
|
||||
}}
|
||||
valueFormat={(v) => `${Number(v).toFixed(2)} kWh`}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user