101 lines
3.1 KiB
TypeScript
101 lines
3.1 KiB
TypeScript
import { GridStressGauge } from '@/components/dashboard/grid-stress-gauge.js';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card.js';
|
|
import { Radio } from 'lucide-react';
|
|
|
|
import { fetchRegionDemandSummary } from '@/actions/demand.js';
|
|
import { deserialize } from '@/lib/superjson.js';
|
|
|
|
interface RegionStatus {
|
|
regionCode: string;
|
|
regionName: string;
|
|
latestDemandMw: number;
|
|
peakDemandMw: number;
|
|
}
|
|
|
|
export async function StressGauges() {
|
|
const demandResult = await fetchRegionDemandSummary();
|
|
|
|
const demandRows = demandResult.ok
|
|
? deserialize<
|
|
Array<{
|
|
avg_demand: number | null;
|
|
peak_demand: number | null;
|
|
region_code: string;
|
|
region_name: string;
|
|
day: Date;
|
|
}>
|
|
>(demandResult.data)
|
|
: [];
|
|
|
|
// For each region: use the most recent day's avg_demand as "current",
|
|
// and the 7-day peak_demand as the ceiling.
|
|
const regionMap = new Map<string, RegionStatus>();
|
|
for (const row of demandRows) {
|
|
const existing = regionMap.get(row.region_code);
|
|
const demand = row.avg_demand ?? 0;
|
|
const peak = row.peak_demand ?? 0;
|
|
|
|
if (!existing) {
|
|
regionMap.set(row.region_code, {
|
|
regionCode: row.region_code,
|
|
regionName: row.region_name,
|
|
latestDemandMw: demand,
|
|
peakDemandMw: peak,
|
|
});
|
|
} else {
|
|
// The query is ordered by day ASC, so later rows overwrite latestDemand
|
|
existing.latestDemandMw = demand;
|
|
if (peak > existing.peakDemandMw) existing.peakDemandMw = peak;
|
|
}
|
|
}
|
|
|
|
const regions = [...regionMap.values()]
|
|
.filter(r => r.peakDemandMw > 0)
|
|
.sort((a, b) => b.latestDemandMw - a.latestDemandMw);
|
|
|
|
if (regions.length === 0) return null;
|
|
|
|
// Split into two columns for wider screens
|
|
const midpoint = Math.ceil(regions.length / 2);
|
|
const leftColumn = regions.slice(0, midpoint);
|
|
const rightColumn = regions.slice(midpoint);
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Radio className="h-5 w-5 text-chart-4" />
|
|
Region Grid Status
|
|
</CardTitle>
|
|
<p className="text-xs text-muted-foreground">Current demand vs. 7-day peak by region</p>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid gap-x-8 gap-y-2.5 md:grid-cols-2">
|
|
<div className="flex flex-col gap-2.5">
|
|
{leftColumn.map(r => (
|
|
<GridStressGauge
|
|
key={r.regionCode}
|
|
regionCode={r.regionCode}
|
|
regionName={r.regionName}
|
|
demandMw={r.latestDemandMw}
|
|
peakDemandMw={r.peakDemandMw}
|
|
/>
|
|
))}
|
|
</div>
|
|
<div className="flex flex-col gap-2.5">
|
|
{rightColumn.map(r => (
|
|
<GridStressGauge
|
|
key={r.regionCode}
|
|
regionCode={r.regionCode}
|
|
regionName={r.regionName}
|
|
demandMw={r.latestDemandMw}
|
|
peakDemandMw={r.peakDemandMw}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|