// Shared app shell: primary sidebar + subrail + top bar.
// Used by dashboard and map views.
const Sidebar = ({ activeModule = 'tree' }) => (
);
const Subrail = ({ view, onView }) => {
const items = [
{ id: 'dashboard', Icon: IDashboard, label: 'Dashboard', href: '../dashboard/dashboard.html' },
{ id: 'list', Icon: IList, label: 'List', href: '../assets/assets.html' },
{ id: 'map', Icon: IMap, label: 'Map', href: '../map/map.html' },
{ id: 'docs', Icon: IDocs, label: 'Documents', href: '#' },
];
// Track filter-panel open state so the funnel icon gets the active style
// (matching the map/dashboard icons) when the panel is visible.
const [filtersOpen, setFiltersOpen] = React.useState(false);
React.useEffect(() => {
const onState = (e) => setFiltersOpen(!!(e.detail && e.detail.open));
window.addEventListener('spotlite:filters-state', onState);
return () => window.removeEventListener('spotlite:filters-state', onState);
}, []);
return (
);
};
const TopBar = ({ crumb, title, children }) => (
);
// ── Span info popover ────────────────────────────────────────────
// Shared across all map views. Pops up on conductor click; shows asset
// designation, tier pill, alarm/alert counts, length, and "View in asset
// list" link. Click X or outside to close.
//
// Props:
// data: { n, tier, alarms, alerts, lengthM } | null
// x, y: click position relative to .map-stage (px)
// onClose: () => void
const SpanInfoPopover = ({ data, x, y, onClose }) => {
const ref = React.useRef(null);
React.useEffect(() => {
if (!data) return;
const onDown = (e) => {
if (ref.current && !ref.current.contains(e.target)) onClose();
};
// Defer one frame so the click that opened us doesn't immediately close it
const t = setTimeout(() => document.addEventListener('mousedown', onDown), 0);
return () => { clearTimeout(t); document.removeEventListener('mousedown', onDown); };
}, [data, onClose]);
if (!data) return null;
const { n, seq, tier, alarms = 0, alerts = 0, lengthM, voltage = '150 kV', infra = 'HV overhead line' } = data;
const tierLabel = tier === 'alarm' ? 'Alarm' : tier === 'alert' ? 'Alert' : 'Stable';
const designation = data.designation || `Span ${seq != null ? seq : n}`;
const popoverW = 280;
// Place to the right of the click; flip left if it would overflow
const left = x + 14;
const top = Math.max(8, y - 24);
return (
Span {seq != null ? seq : n} — T{n}→T{n+1}
{designation}
{tierLabel}
Infrastructure{infra}
Voltage{voltage}
{lengthM != null &&
Span length{Math.round(lengthM)} m
}
Alarm trees 0 ? 'alarm' : '')}>{alarms}
Alert trees 0 ? 'alert' : '')}>{alerts}
);
};
Object.assign(window, { Sidebar, Subrail, TopBar, SpanInfoPopover });