116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useState } from "react";
|
|
import { AuditLogDrawer } from "@/components/mission-control/AuditLogDrawer";
|
|
import { GlobalAgentRoster } from "@/components/mission-control/GlobalAgentRoster";
|
|
import {
|
|
MAX_PANEL_COUNT,
|
|
MIN_PANEL_COUNT,
|
|
MissionControlPanel,
|
|
type PanelConfig,
|
|
} from "@/components/mission-control/MissionControlPanel";
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
const INITIAL_PANELS: PanelConfig[] = [{}];
|
|
|
|
export function MissionControlLayout(): React.JSX.Element {
|
|
const [panels, setPanels] = useState<PanelConfig[]>(INITIAL_PANELS);
|
|
const [selectedSessionId, setSelectedSessionId] = useState<string>();
|
|
|
|
const handleSelectSession = useCallback((sessionId: string): void => {
|
|
setSelectedSessionId(sessionId);
|
|
|
|
setPanels((currentPanels) => {
|
|
if (currentPanels.some((panel) => panel.sessionId === sessionId)) {
|
|
return currentPanels;
|
|
}
|
|
|
|
const firstEmptyPanelIndex = currentPanels.findIndex(
|
|
(panel) => panel.sessionId === undefined
|
|
);
|
|
if (firstEmptyPanelIndex >= 0) {
|
|
return currentPanels.map((panel, index) =>
|
|
index === firstEmptyPanelIndex ? { ...panel, sessionId } : panel
|
|
);
|
|
}
|
|
|
|
if (currentPanels.length >= MAX_PANEL_COUNT) {
|
|
return currentPanels;
|
|
}
|
|
|
|
return [...currentPanels, { sessionId }];
|
|
});
|
|
}, []);
|
|
|
|
const handleAddPanel = useCallback((): void => {
|
|
setPanels((currentPanels) => {
|
|
if (currentPanels.length >= MAX_PANEL_COUNT) {
|
|
return currentPanels;
|
|
}
|
|
|
|
return [...currentPanels, {}];
|
|
});
|
|
}, []);
|
|
|
|
const handleRemovePanel = useCallback((panelIndex: number): void => {
|
|
setPanels((currentPanels) => {
|
|
if (panelIndex < 0 || panelIndex >= currentPanels.length) {
|
|
return currentPanels;
|
|
}
|
|
|
|
if (currentPanels.length <= MIN_PANEL_COUNT) {
|
|
return currentPanels;
|
|
}
|
|
|
|
const nextPanels = currentPanels.filter((_, index) => index !== panelIndex);
|
|
return nextPanels.length === 0 ? INITIAL_PANELS : nextPanels;
|
|
});
|
|
}, []);
|
|
|
|
const handleExpandPanel = useCallback((panelIndex: number): void => {
|
|
setPanels((currentPanels) => {
|
|
if (panelIndex < 0 || panelIndex >= currentPanels.length) {
|
|
return currentPanels;
|
|
}
|
|
|
|
const shouldExpand = !currentPanels[panelIndex]?.expanded;
|
|
|
|
return currentPanels.map((panel, index) => ({
|
|
...panel,
|
|
expanded: shouldExpand && index === panelIndex,
|
|
}));
|
|
});
|
|
}, []);
|
|
|
|
return (
|
|
<section className="flex h-full min-h-0 flex-col overflow-hidden" aria-label="Mission Control">
|
|
<header className="mb-3 flex items-center justify-end">
|
|
<AuditLogDrawer
|
|
trigger={
|
|
<Button variant="outline" size="sm">
|
|
Audit Log
|
|
</Button>
|
|
}
|
|
/>
|
|
</header>
|
|
|
|
<div className="grid min-h-0 flex-1 gap-4 xl:grid-cols-[280px_minmax(0,1fr)]">
|
|
<aside className="h-full min-h-0">
|
|
<GlobalAgentRoster
|
|
onSelectSession={handleSelectSession}
|
|
{...(selectedSessionId !== undefined ? { selectedSessionId } : {})}
|
|
/>
|
|
</aside>
|
|
<main className="h-full min-h-0 overflow-hidden">
|
|
<MissionControlPanel
|
|
panels={panels}
|
|
onAddPanel={handleAddPanel}
|
|
onRemovePanel={handleRemovePanel}
|
|
onExpandPanel={handleExpandPanel}
|
|
/>
|
|
</main>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|