uprava adutiu - nacitani dalsich registru, uprava ekonomiky
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
||||
CartesianGrid,
|
||||
Cell,
|
||||
ComposedChart,
|
||||
Legend,
|
||||
Line,
|
||||
ReferenceLine,
|
||||
ResponsiveContainer,
|
||||
@@ -14,11 +15,14 @@ import type { ChartDayPoint } from '../../types/economics'
|
||||
|
||||
type Props = {
|
||||
points: ChartDayPoint[]
|
||||
hasGreenBonus: boolean
|
||||
}
|
||||
|
||||
const GREEN = '#22c55e'
|
||||
const RED = '#ef4444'
|
||||
const BLUE = '#3b82f6'
|
||||
const AMBER = '#f59e0b'
|
||||
const SLATE = '#64748b'
|
||||
|
||||
function formatDay(iso: string): string {
|
||||
const d = new Date(iso + 'T00:00:00')
|
||||
@@ -29,40 +33,66 @@ type PayloadEntry = {
|
||||
name?: string
|
||||
value?: number
|
||||
color?: string
|
||||
dataKey?: string
|
||||
}
|
||||
|
||||
function CustomTooltip({
|
||||
active,
|
||||
payload,
|
||||
label,
|
||||
hasGreenBonus,
|
||||
}: {
|
||||
active?: boolean
|
||||
payload?: PayloadEntry[]
|
||||
label?: string
|
||||
hasGreenBonus: boolean
|
||||
}) {
|
||||
if (!active || !payload?.length || !label) return null
|
||||
const balance = payload.find((p) => p.name === 'daily_balance_czk')
|
||||
const cumulative = payload.find((p) => p.name === 'cumulative_balance_czk')
|
||||
|
||||
const gridBalance = payload.find((p) => p.dataKey === 'daily_grid_balance_czk')
|
||||
const bonus = payload.find((p) => p.dataKey === 'daily_green_bonus_czk')
|
||||
const cumBalance = payload.find((p) => p.dataKey === 'cumulative_balance_czk')
|
||||
const cumGrid = payload.find((p) => p.dataKey === 'cumulative_grid_balance_czk')
|
||||
const importCost = payload.find((p) => p.dataKey === 'daily_import_cost_czk')
|
||||
const exportRev = payload.find((p) => p.dataKey === 'daily_export_revenue_czk')
|
||||
|
||||
const gridVal = gridBalance?.value ?? 0
|
||||
const bonusVal = bonus?.value ?? 0
|
||||
const total = gridVal + bonusVal
|
||||
|
||||
const fmtCzk = (v: number) => `${v >= 0 ? '+' : ''}${v.toFixed(2)} Kč`
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-slate-700 bg-slate-800 px-3 py-2 text-xs shadow-lg">
|
||||
<p className="mb-1 font-medium text-slate-200">{label}</p>
|
||||
{balance && (
|
||||
<p style={{ color: (balance.value ?? 0) >= 0 ? GREEN : RED }}>
|
||||
Den: {(balance.value ?? 0) >= 0 ? '+' : ''}
|
||||
{(balance.value ?? 0).toFixed(2)} Kč
|
||||
<p style={{ color: gridVal >= 0 ? GREEN : RED }}>Síť: {fmtCzk(gridVal)}</p>
|
||||
{hasGreenBonus && <p style={{ color: AMBER }}>Bonus: {fmtCzk(bonusVal)}</p>}
|
||||
<p className="mt-1 border-t border-slate-700 pt-1 font-semibold" style={{ color: total >= 0 ? GREEN : RED }}>
|
||||
Celkem: {fmtCzk(total)}
|
||||
</p>
|
||||
{importCost && (
|
||||
<p className="mt-1 text-slate-400">
|
||||
Nákup ze sítě: {(importCost.value ?? 0).toFixed(2)} Kč
|
||||
</p>
|
||||
)}
|
||||
{cumulative && (
|
||||
<p style={{ color: BLUE }}>
|
||||
Kumulativ: {(cumulative.value ?? 0) >= 0 ? '+' : ''}
|
||||
{(cumulative.value ?? 0).toFixed(2)} Kč
|
||||
{exportRev && (
|
||||
<p className="text-slate-400">Prodej do sítě: {(exportRev.value ?? 0).toFixed(2)} Kč</p>
|
||||
)}
|
||||
{cumBalance && (
|
||||
<p className="mt-1 text-slate-400" style={{ color: BLUE }}>
|
||||
Kumulativ: {fmtCzk(cumBalance.value ?? 0)}
|
||||
</p>
|
||||
)}
|
||||
{cumGrid && (
|
||||
<p className="text-slate-400" style={{ color: SLATE }}>
|
||||
Kumulativ síť: {fmtCzk(cumGrid.value ?? 0)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function EconomicsChart({ points }: Props) {
|
||||
export function EconomicsChart({ points, hasGreenBonus }: Props) {
|
||||
if (points.length === 0) {
|
||||
return (
|
||||
<div className="flex h-64 items-center justify-center text-sm text-slate-500">
|
||||
@@ -77,7 +107,7 @@ export function EconomicsChart({ points }: Props) {
|
||||
}))
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={320}>
|
||||
<ResponsiveContainer width="100%" height={380}>
|
||||
<ComposedChart data={data} margin={{ top: 8, right: 16, bottom: 4, left: 0 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
|
||||
<XAxis dataKey="label" tick={{ fontSize: 11, fill: '#94a3b8' }} />
|
||||
@@ -102,13 +132,72 @@ export function EconomicsChart({ points }: Props) {
|
||||
style: { fontSize: 11, fill: BLUE },
|
||||
}}
|
||||
/>
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<Tooltip content={<CustomTooltip hasGreenBonus={hasGreenBonus} />} />
|
||||
<Legend
|
||||
wrapperStyle={{ fontSize: 11 }}
|
||||
formatter={(value: string) => {
|
||||
const labels: Record<string, string> = {
|
||||
daily_grid_balance_czk: 'Bilance síť',
|
||||
daily_green_bonus_czk: 'Zelený bonus',
|
||||
daily_import_cost_czk: 'Nákup ze sítě',
|
||||
daily_export_revenue_czk: 'Prodej do sítě',
|
||||
cumulative_balance_czk: 'Kumulativ vč. bonusu',
|
||||
cumulative_grid_balance_czk: 'Kumulativ síť',
|
||||
}
|
||||
return labels[value] ?? value
|
||||
}}
|
||||
/>
|
||||
<ReferenceLine yAxisId="left" y={0} stroke="#475569" strokeDasharray="2 2" />
|
||||
<Bar yAxisId="left" dataKey="daily_balance_czk" radius={[3, 3, 0, 0]} maxBarSize={32}>
|
||||
|
||||
{/* Stacked bars: bottom = grid balance, top = green bonus */}
|
||||
<Bar
|
||||
yAxisId="left"
|
||||
dataKey="daily_grid_balance_czk"
|
||||
stackId="balance"
|
||||
maxBarSize={28}
|
||||
radius={hasGreenBonus ? undefined : [3, 3, 0, 0]}
|
||||
>
|
||||
{data.map((entry, idx) => (
|
||||
<Cell key={idx} fill={entry.daily_balance_czk >= 0 ? GREEN : RED} fillOpacity={0.8} />
|
||||
<Cell
|
||||
key={idx}
|
||||
fill={entry.daily_grid_balance_czk >= 0 ? GREEN : RED}
|
||||
fillOpacity={0.7}
|
||||
/>
|
||||
))}
|
||||
</Bar>
|
||||
{hasGreenBonus && (
|
||||
<Bar
|
||||
yAxisId="left"
|
||||
dataKey="daily_green_bonus_czk"
|
||||
stackId="balance"
|
||||
fill={AMBER}
|
||||
fillOpacity={0.8}
|
||||
maxBarSize={28}
|
||||
radius={[3, 3, 0, 0]}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Lines: import cost / export revenue */}
|
||||
<Line
|
||||
yAxisId="left"
|
||||
type="monotone"
|
||||
dataKey="daily_import_cost_czk"
|
||||
stroke={RED}
|
||||
strokeWidth={1.5}
|
||||
strokeDasharray="4 2"
|
||||
dot={false}
|
||||
/>
|
||||
<Line
|
||||
yAxisId="left"
|
||||
type="monotone"
|
||||
dataKey="daily_export_revenue_czk"
|
||||
stroke={GREEN}
|
||||
strokeWidth={1.5}
|
||||
strokeDasharray="4 2"
|
||||
dot={false}
|
||||
/>
|
||||
|
||||
{/* Cumulative lines (right axis) */}
|
||||
<Line
|
||||
yAxisId="right"
|
||||
type="monotone"
|
||||
@@ -117,6 +206,15 @@ export function EconomicsChart({ points }: Props) {
|
||||
strokeWidth={2}
|
||||
dot={{ r: 3, fill: BLUE }}
|
||||
/>
|
||||
<Line
|
||||
yAxisId="right"
|
||||
type="monotone"
|
||||
dataKey="cumulative_grid_balance_czk"
|
||||
stroke={SLATE}
|
||||
strokeWidth={1.5}
|
||||
strokeDasharray="5 3"
|
||||
dot={false}
|
||||
/>
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
)
|
||||
|
||||
@@ -143,6 +143,7 @@ function DailyRow({
|
||||
onToggle: () => void
|
||||
onLockToggle: () => void
|
||||
}) {
|
||||
const colCount = hasGreenBonus ? 13 : 12
|
||||
return (
|
||||
<>
|
||||
<tr
|
||||
@@ -157,9 +158,17 @@ function DailyRow({
|
||||
</td>
|
||||
<td className="px-3 py-2 text-right text-sm">{kwh(row.import_kwh)}</td>
|
||||
<td className="px-3 py-2 text-right text-sm">{kwh(row.export_kwh)}</td>
|
||||
<td className="px-3 py-2 text-right text-sm text-slate-400">{kwh(row.self_consumption_kwh)}</td>
|
||||
<td className="px-3 py-2 text-right text-sm text-red-400">{row.import_cost_czk.toFixed(2)}</td>
|
||||
<td className="px-3 py-2 text-right text-sm text-green-400">{row.export_revenue_czk.toFixed(2)}</td>
|
||||
<td className="px-3 py-2 text-right text-sm text-slate-400">{kwh(row.pv_self_consumption_kwh)}</td>
|
||||
<td className="px-3 py-2 text-right text-sm text-red-400">{row.grid_import_cashflow_czk.toFixed(2)}</td>
|
||||
<td className="px-3 py-2 text-right text-sm text-green-400">{row.grid_export_revenue_czk.toFixed(2)}</td>
|
||||
<td className="px-3 py-2 text-right text-sm text-red-300">{row.import_cost_czk.toFixed(2)}</td>
|
||||
<td className="px-3 py-2 text-right text-sm text-green-300">{row.export_revenue_czk.toFixed(2)}</td>
|
||||
<td className="px-3 py-2 text-right text-sm text-slate-400">
|
||||
{row.planned_balance_czk != null ? czk(row.planned_balance_czk) : '–'}
|
||||
</td>
|
||||
<td className={`px-3 py-2 text-right text-sm ${row.deviation_cost_czk != null ? balanceColor(-row.deviation_cost_czk) : ''}`}>
|
||||
{row.deviation_cost_czk != null ? czk(row.deviation_cost_czk) : '–'}
|
||||
</td>
|
||||
{hasGreenBonus && (
|
||||
<td className="px-3 py-2 text-right text-sm text-amber-400">
|
||||
{row.green_bonus_czk > 0 ? row.green_bonus_czk.toFixed(2) : '–'}
|
||||
@@ -168,12 +177,6 @@ function DailyRow({
|
||||
<td className={`px-3 py-2 text-right text-sm font-semibold ${balanceColor(row.total_balance_czk)}`}>
|
||||
{czk(row.total_balance_czk)}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-right text-sm text-slate-400">
|
||||
{row.planned_balance_czk != null ? czk(row.planned_balance_czk) : '–'}
|
||||
</td>
|
||||
<td className={`px-3 py-2 text-right text-sm ${row.deviation_cost_czk != null ? balanceColor(-row.deviation_cost_czk) : ''}`}>
|
||||
{row.deviation_cost_czk != null ? czk(row.deviation_cost_czk) : '–'}
|
||||
</td>
|
||||
<td className="px-2 py-2 text-center">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
@@ -193,7 +196,7 @@ function DailyRow({
|
||||
</tr>
|
||||
{expanded && (
|
||||
<tr>
|
||||
<td colSpan={hasGreenBonus ? 11 : 10} className="bg-slate-900/50 px-4 py-2">
|
||||
<td colSpan={colCount} className="bg-slate-900/50 px-4 py-2">
|
||||
<IntervalDetail siteId={siteId} day={row.day} hasGreenBonus={hasGreenBonus} />
|
||||
</td>
|
||||
</tr>
|
||||
@@ -217,6 +220,8 @@ export default function Economics() {
|
||||
return {
|
||||
import_cost: days.reduce((s, d) => s + d.import_cost_czk, 0),
|
||||
export_revenue: days.reduce((s, d) => s + d.export_revenue_czk, 0),
|
||||
grid_import_cashflow: days.reduce((s, d) => s + d.grid_import_cashflow_czk, 0),
|
||||
grid_export_revenue: days.reduce((s, d) => s + d.grid_export_revenue_czk, 0),
|
||||
green_bonus: days.reduce((s, d) => s + d.green_bonus_czk, 0),
|
||||
total_balance: days.reduce((s, d) => s + d.total_balance_czk, 0),
|
||||
}
|
||||
@@ -277,14 +282,22 @@ export default function Economics() {
|
||||
|
||||
{/* Summary cards */}
|
||||
{summary && (
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-6">
|
||||
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
||||
<p className="text-xs text-slate-400">Nákup celkem</p>
|
||||
<p className="mt-1 text-lg font-semibold text-red-400">{summary.import_cost.toFixed(2)} Kč</p>
|
||||
<p className="text-xs text-slate-400">Nákup ze sítě</p>
|
||||
<p className="mt-1 text-lg font-semibold text-red-400">{summary.grid_import_cashflow.toFixed(2)} Kč</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
||||
<p className="text-xs text-slate-400">Prodej celkem</p>
|
||||
<p className="mt-1 text-lg font-semibold text-green-400">{summary.export_revenue.toFixed(2)} Kč</p>
|
||||
<p className="text-xs text-slate-400">Prodej do sítě</p>
|
||||
<p className="mt-1 text-lg font-semibold text-green-400">{summary.grid_export_revenue.toFixed(2)} Kč</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
||||
<p className="text-xs text-slate-400">Náklad celkem</p>
|
||||
<p className="mt-1 text-lg font-semibold text-red-300">{summary.import_cost.toFixed(2)} Kč</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
||||
<p className="text-xs text-slate-400">Příjem celkem</p>
|
||||
<p className="mt-1 text-lg font-semibold text-green-300">{summary.export_revenue.toFixed(2)} Kč</p>
|
||||
</div>
|
||||
{hasGreenBonus && (
|
||||
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
||||
@@ -304,9 +317,9 @@ export default function Economics() {
|
||||
{/* Chart */}
|
||||
<div className="rounded-xl border border-slate-800 bg-slate-900 p-4">
|
||||
<h2 className="mb-3 text-sm font-medium text-slate-300">
|
||||
Denní bilance + kumulativ od 1. v měsíci
|
||||
Denní bilance (síť + bonus) a kumulativ
|
||||
</h2>
|
||||
<EconomicsChart points={points} />
|
||||
<EconomicsChart points={points} hasGreenBonus={hasGreenBonus} />
|
||||
</div>
|
||||
|
||||
{/* Daily table */}
|
||||
@@ -348,13 +361,15 @@ export default function Economics() {
|
||||
<th className="px-3 py-2 text-left">Den</th>
|
||||
<th className="px-3 py-2 text-right">Import kWh</th>
|
||||
<th className="px-3 py-2 text-right">Export kWh</th>
|
||||
<th className="px-3 py-2 text-right">Vl. spotřeba</th>
|
||||
<th className="px-3 py-2 text-right">FVE vl. spot.</th>
|
||||
<th className="px-3 py-2 text-right">Nákup ze sítě</th>
|
||||
<th className="px-3 py-2 text-right">Prodej do sítě</th>
|
||||
<th className="px-3 py-2 text-right">Náklad Kč</th>
|
||||
<th className="px-3 py-2 text-right">Příjem Kč</th>
|
||||
{hasGreenBonus && <th className="px-3 py-2 text-right">Bonus Kč</th>}
|
||||
<th className="px-3 py-2 text-right">Bilance Kč</th>
|
||||
<th className="px-3 py-2 text-right">Plán Kč</th>
|
||||
<th className="px-3 py-2 text-right">Odchylka Kč</th>
|
||||
{hasGreenBonus && <th className="px-3 py-2 text-right">Bonus Kč</th>}
|
||||
<th className="px-3 py-2 text-right">Bilance Kč</th>
|
||||
<th className="w-10 px-2 py-2 text-center" />
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -5,11 +5,13 @@ export type DailyEconomics = {
|
||||
export_kwh: number
|
||||
pv_kwh: number
|
||||
load_kwh: number
|
||||
self_consumption_kwh: number
|
||||
pv_self_consumption_kwh: number
|
||||
ev_kwh: number
|
||||
hp_kwh: number
|
||||
import_cost_czk: number
|
||||
export_revenue_czk: number
|
||||
grid_import_cashflow_czk: number
|
||||
grid_export_revenue_czk: number
|
||||
net_cost_czk: number
|
||||
green_bonus_czk: number
|
||||
total_balance_czk: number
|
||||
@@ -28,6 +30,8 @@ export type IntervalEconomics = {
|
||||
import_kwh: number
|
||||
export_kwh: number
|
||||
dynamic_cost_czk: number | null
|
||||
grid_import_cashflow_czk: number | null
|
||||
grid_export_revenue_czk: number | null
|
||||
stored_cost_czk: number | null
|
||||
green_bonus_czk: number | null
|
||||
planned_cost_czk: number | null
|
||||
@@ -46,7 +50,12 @@ export type IntervalEconomics = {
|
||||
export type ChartDayPoint = {
|
||||
day: string
|
||||
daily_balance_czk: number
|
||||
daily_grid_balance_czk: number
|
||||
daily_green_bonus_czk: number
|
||||
daily_import_cost_czk: number
|
||||
daily_export_revenue_czk: number
|
||||
cumulative_balance_czk: number
|
||||
cumulative_grid_balance_czk: number
|
||||
}
|
||||
|
||||
export type LockResponse = {
|
||||
|
||||
Reference in New Issue
Block a user