flwo - denni sankey graf
This commit is contained in:
@@ -28,7 +28,7 @@ Základní 6 Wh veličin (import/export, PV, baterie, load) zůstává ve Fázi
|
|||||||
|
|
||||||
## UI
|
## UI
|
||||||
|
|
||||||
- Sankey (`@nivo/sankey`) – součet toků za zvolený měsíc. Síť je ve vizualizaci rozdělena na **Import ze sítě** a **Export do sítě** (jinak by vznikl cyklus síť↔baterie a knihovna hlásí „circular link“).
|
- Sankey (`@nivo/sankey`) – součet toků za **celý měsíc** nebo za **jeden vybraný den** (rozbalovací pole „Graf a karty“; klik na název dne v tabulce také přepne den). Síť je ve vizualizaci rozdělena na **Import ze sítě** a **Export do sítě** (jinak by vznikl cyklus síť↔baterie a knihovna hlásí „circular link“).
|
||||||
- Tři perspektivní karty (FVE / síť / baterie).
|
- Tři perspektivní karty (FVE / síť / baterie).
|
||||||
- Tabulka dnů s rozbalením na 15min intervaly.
|
- Tabulka dnů s rozbalením na 15min intervaly.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from 'lucide-react'
|
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from 'lucide-react'
|
||||||
import { Fragment, useMemo, useState } from 'react'
|
import { Fragment, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
import { EnergyFlowSankey, type FlowTotals } from '../components/EnergyFlowSankey'
|
import { EnergyFlowSankey, type FlowTotals } from '../components/EnergyFlowSankey'
|
||||||
import { useEnergyFlowsDaily, useEnergyFlowsIntervals } from '../hooks/useEnergyFlows'
|
import { useEnergyFlowsDaily, useEnergyFlowsIntervals } from '../hooks/useEnergyFlows'
|
||||||
@@ -132,10 +132,23 @@ export default function EnergyFlows() {
|
|||||||
|
|
||||||
const [month, setMonth] = useState(currentMonth)
|
const [month, setMonth] = useState(currentMonth)
|
||||||
const [expandedDay, setExpandedDay] = useState<string | null>(null)
|
const [expandedDay, setExpandedDay] = useState<string | null>(null)
|
||||||
|
/** null = součet za celý měsíc; jinak ISO den pro Sankey + perspektivní karty */
|
||||||
|
const [scopeDay, setScopeDay] = useState<string | null>(null)
|
||||||
|
|
||||||
const { days, loading, error, reload } = useEnergyFlowsDaily(siteId, month)
|
const { days, loading, error, reload } = useEnergyFlowsDaily(siteId, month)
|
||||||
|
|
||||||
const totals = useMemo(() => (days.length > 0 ? aggregateFlows(days) : null), [days])
|
useEffect(() => {
|
||||||
|
setScopeDay(null)
|
||||||
|
}, [month])
|
||||||
|
|
||||||
|
const totals = useMemo(() => {
|
||||||
|
if (days.length === 0) return null
|
||||||
|
if (scopeDay) {
|
||||||
|
const row = days.find((d) => d.day === scopeDay)
|
||||||
|
if (row) return aggregateFlows([row])
|
||||||
|
}
|
||||||
|
return aggregateFlows(days)
|
||||||
|
}, [days, scopeDay])
|
||||||
|
|
||||||
const flowOnly: FlowTotals | null = totals
|
const flowOnly: FlowTotals | null = totals
|
||||||
? {
|
? {
|
||||||
@@ -201,6 +214,30 @@ export default function EnergyFlows() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
{days.length > 0 && (
|
||||||
|
<div className="flex flex-wrap items-center gap-3 rounded-lg border border-slate-800 bg-slate-900/60 px-3 py-2">
|
||||||
|
<label htmlFor="energy-flow-scope" className="text-sm text-slate-400">
|
||||||
|
Graf a karty:
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="energy-flow-scope"
|
||||||
|
className="max-w-[min(100%,20rem)] rounded-lg border border-slate-700 bg-slate-900 px-3 py-1.5 text-sm text-slate-200"
|
||||||
|
value={scopeDay ?? ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const v = e.target.value
|
||||||
|
setScopeDay(v === '' ? null : v)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="">Celý měsíc (součet)</option>
|
||||||
|
{days.map((d) => (
|
||||||
|
<option key={d.day} value={d.day}>
|
||||||
|
{fmtDay(d.day)}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{totals && (
|
{totals && (
|
||||||
<div className="grid gap-3 sm:grid-cols-3">
|
<div className="grid gap-3 sm:grid-cols-3">
|
||||||
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
||||||
@@ -243,7 +280,9 @@ export default function EnergyFlows() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
||||||
<h2 className="mb-2 text-sm font-medium text-slate-300">Sankey — součet za měsíc</h2>
|
<h2 className="mb-2 text-sm font-medium text-slate-300">
|
||||||
|
Sankey — {scopeDay ? fmtDay(scopeDay) : 'celý měsíc (součet)'}
|
||||||
|
</h2>
|
||||||
<EnergyFlowSankey totals={flowOnly} />
|
<EnergyFlowSankey totals={flowOnly} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -281,7 +320,19 @@ export default function EnergyFlows() {
|
|||||||
<span className="mr-1 inline-block w-4">
|
<span className="mr-1 inline-block w-4">
|
||||||
{expandedDay === row.day ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
|
{expandedDay === row.day ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
|
||||||
</span>
|
</span>
|
||||||
{fmtDay(row.day)}
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`rounded px-1 text-left underline-offset-2 hover:underline ${
|
||||||
|
scopeDay === row.day ? 'text-amber-300' : ''
|
||||||
|
}`}
|
||||||
|
title="Zobrazit tento den v Sankey a v kartách"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setScopeDay(row.day)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{fmtDay(row.day)}
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-right text-sm">{row.pv_production_kwh.toFixed(1)}</td>
|
<td className="px-3 py-2 text-right text-sm">{row.pv_production_kwh.toFixed(1)}</td>
|
||||||
<td className="px-3 py-2 text-right text-sm">{row.grid_import_kwh.toFixed(2)}</td>
|
<td className="px-3 py-2 text-right text-sm">{row.grid_import_kwh.toFixed(2)}</td>
|
||||||
|
|||||||
Reference in New Issue
Block a user