// Copyright (c) 2025-2026, RTE (https://www.rte-france.com) // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, // you can obtain one at http://mozilla.org/MPL/2.0/. // SPDX-License-Identifier: MPL-2.0 // This file is part of Co-Study4Grid a Power Grid Study tool Assistant Interface to help solve contigencies for a grid state under study. import React from 'react'; import { colors, space, text } from '../styles/tokens'; interface OverloadPanelProps { nOverloads: string[]; n1Overloads: string[]; /** * Per-line loading ratios (aligned with `nOverloads`). * Rendered as "(XX.X%)" after the line name. */ nOverloadsRho?: number[]; /** * Per-line loading ratios (aligned with `n1Overloads`). * Rendered as "(XX.X%)" after the line name. */ n1OverloadsRho?: number[]; /** * Clicking an overloaded line switches to the matching diagram * tab (N for N-overloads, N-1 for N-1-overloads) and zooms on * the element — mirroring the old "Loading Before" behavior on * action cards so the operator lands directly on the relevant * network state. */ onAssetClick: (actionId: string, assetName: string, tab?: 'n' | 'contingency') => void; /** * Inline contextual hint shown beneath the heading. Replaces the * previous full yellow banner — the full notice now lives in * NoticesPanel (tier-warning-system PR). Pass a short string like "130/150 lines * monitored — see Notices for details" or leave undefined. */ monitoringHint?: string | null; selectedOverloads?: Set; onToggleOverload?: (overload: string) => void; monitorDeselected?: boolean; onToggleMonitorDeselected?: () => void; /** Resolve an element ID to its human-readable display name. Falls back to the ID. */ displayName?: (id: string) => string; } const OverloadPanel: React.FC = ({ nOverloads, n1Overloads, nOverloadsRho, n1OverloadsRho, onAssetClick, monitoringHint, selectedOverloads, onToggleOverload, monitorDeselected = false, onToggleMonitorDeselected, displayName = (id: string) => id, }) => { const clickableLinkStyle: React.CSSProperties = { background: 'none', border: 'none', cursor: 'pointer', padding: 0, fontSize: 'inherit', color: colors.brand, fontWeight: 600, textDecoration: 'underline dotted', textAlign: 'left', display: 'inline', }; const formatRho = (v: number | undefined) => v == null || Number.isNaN(v) ? null : `${(v * 100).toFixed(1)}%`; const renderLinks = (lines: string[], rhos: number[] | undefined, tab: 'n' | 'contingency') => { if (!lines || lines.length === 0) return None; return lines.map((lineName, i) => { const isSelected = tab === 'contingency' ? (selectedOverloads?.has(lineName) ?? true) : true; const rhoPct = formatRho(rhos?.[i]); return ( {i > 0 && ', '} {rhoPct && ( ({rhoPct}) )} ); }); }; const hasDeselected = n1Overloads.some(name => !(selectedOverloads?.has(name) ?? true)); return (

⚠️ Overloads

{monitoringHint && (
{monitoringHint}
)}
0 ? colors.warningSoft : 'transparent', borderLeft: `3px solid ${nOverloads.length > 0 ? 'var(--color-warning)' : 'transparent'}`, borderBottom: `1px solid ${colors.borderSubtle}` }}> N Overloads:
{renderLinks(nOverloads, nOverloadsRho, 'n')}
0 ? colors.dangerSoft : 'transparent', borderLeft: `3px solid ${n1Overloads.length > 0 ? 'var(--color-danger)' : 'transparent'}`, borderBottom: `1px solid ${colors.borderSubtle}`, lineHeight: '1.6', }}> N-1 Overloads: ? {hasDeselected && onToggleMonitorDeselected && ( )} {renderLinks(n1Overloads, n1OverloadsRho, 'contingency')}
); }; export default React.memo(OverloadPanel);