feat(web): MS23-P2-008 panel grid responsive layout
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
This commit is contained in:
@@ -1,21 +1,86 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { AuditLogDrawer } from "@/components/mission-control/AuditLogDrawer";
|
||||
import { GlobalAgentRoster } from "@/components/mission-control/GlobalAgentRoster";
|
||||
import { MissionControlPanel } from "@/components/mission-control/MissionControlPanel";
|
||||
import {
|
||||
MAX_PANEL_COUNT,
|
||||
MIN_PANEL_COUNT,
|
||||
MissionControlPanel,
|
||||
type PanelConfig,
|
||||
} from "@/components/mission-control/MissionControlPanel";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useSessions } from "@/hooks/useMissionControl";
|
||||
|
||||
const DEFAULT_PANEL_SLOTS = ["panel-1", "panel-2", "panel-3", "panel-4"] as const;
|
||||
const INITIAL_PANELS: PanelConfig[] = [{}];
|
||||
|
||||
export function MissionControlLayout(): React.JSX.Element {
|
||||
const { sessions } = useSessions();
|
||||
const [panels, setPanels] = useState<PanelConfig[]>(INITIAL_PANELS);
|
||||
const [selectedSessionId, setSelectedSessionId] = useState<string>();
|
||||
|
||||
// First panel: selected session (from roster click) or first available session
|
||||
const firstPanelSessionId = selectedSessionId ?? sessions[0]?.id;
|
||||
const panelSessionIds = [firstPanelSessionId, undefined, undefined, undefined] as const;
|
||||
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">
|
||||
@@ -32,12 +97,17 @@ export function MissionControlLayout(): React.JSX.Element {
|
||||
<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={setSelectedSessionId}
|
||||
onSelectSession={handleSelectSession}
|
||||
{...(selectedSessionId !== undefined ? { selectedSessionId } : {})}
|
||||
/>
|
||||
</aside>
|
||||
<main className="h-full min-h-0 overflow-hidden">
|
||||
<MissionControlPanel panels={DEFAULT_PANEL_SLOTS} panelSessionIds={panelSessionIds} />
|
||||
<MissionControlPanel
|
||||
panels={panels}
|
||||
onAddPanel={handleAddPanel}
|
||||
onRemovePanel={handleRemovePanel}
|
||||
onExpandPanel={handleExpandPanel}
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user