redesign automation page with simple list + detail panels
Replace 8-category layout with flat task list and detail view using WorkspaceView. Searchable list with mode badges, run/delete actions, create dialog. Remove 13 unused component files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,100 +1,41 @@
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { useIsMobile } from 'hooks/useIsMobile';
|
||||
import { usePanelChannel } from 'hooks/usePanelChannel';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { LayoutNode, PanelComponents } from 'officerdev';
|
||||
import { WorkspaceView } from 'officerdev';
|
||||
import { useIsMobile } from 'hooks/useIsMobile';
|
||||
import { usePanelChannel } from 'hooks/usePanelChannel';
|
||||
import { useDashboardState } from 'state/useDashboardState';
|
||||
import type { TaskSummary } from 'officerdev';
|
||||
|
||||
import { AutomationRightPanel } from './AutomationRightPanel';
|
||||
import type { AutomationSelection } from './AutomationRightPanel';
|
||||
import { AutomationEditChat } from './AutomationEditChat';
|
||||
import { AutomationSidebar } from './AutomationSidebar';
|
||||
import { AutomationList } from './AutomationList';
|
||||
import { AutomationDetail } from './AutomationDetail';
|
||||
|
||||
const CHAT_PANEL_ID = 'automation-chat';
|
||||
export type { TaskSummary as SelectedTask };
|
||||
|
||||
const defaultLayout: LayoutNode = {
|
||||
type: 'group',
|
||||
id: 'automation-root',
|
||||
direction: 'horizontal',
|
||||
children: [
|
||||
{ node: { type: 'panel', id: 'automation-left', appType: null }, size: 20 },
|
||||
{ node: { type: 'panel', id: 'automation-right', appType: null }, size: 80 },
|
||||
{ node: { type: 'panel', id: 'automation-list', appType: null }, size: 30 },
|
||||
{ node: { type: 'panel', id: 'automation-detail', appType: null }, size: 70 },
|
||||
],
|
||||
};
|
||||
|
||||
const hasChatPanel = (layout: LayoutNode): boolean => {
|
||||
if (layout.type === 'panel') return layout.id === CHAT_PANEL_ID;
|
||||
return layout.children.some((c) => hasChatPanel(c.node));
|
||||
};
|
||||
|
||||
const addChatPanel = (layout: LayoutNode): LayoutNode => {
|
||||
if (layout.type !== 'group') return layout;
|
||||
const clone = structuredClone(layout);
|
||||
const rightChild = clone.children[1];
|
||||
if (!rightChild) return clone;
|
||||
|
||||
// Wrap the right panel in a vertical group with the chat panel
|
||||
const rightSize = rightChild.size;
|
||||
rightChild.size = rightSize;
|
||||
clone.children[1] = {
|
||||
node: {
|
||||
type: 'group',
|
||||
id: 'automation-right-group',
|
||||
direction: 'vertical',
|
||||
children: [
|
||||
{ node: rightChild.node, size: 50 },
|
||||
{ node: { type: 'panel', id: CHAT_PANEL_ID, appType: null }, size: 50 },
|
||||
],
|
||||
},
|
||||
size: rightSize,
|
||||
};
|
||||
return clone;
|
||||
};
|
||||
|
||||
const removeChatPanel = (layout: LayoutNode): LayoutNode => {
|
||||
if (layout.type !== 'group') return layout;
|
||||
const clone = structuredClone(layout);
|
||||
const rightChild = clone.children[1];
|
||||
if (!rightChild || rightChild.node.type !== 'group') return clone;
|
||||
|
||||
// Unwrap: pull the right panel out of the vertical group
|
||||
const rightGroup = rightChild.node;
|
||||
const mainPanel = rightGroup.children.find((c) => c.node.type === 'panel' && (c.node as { id: string }).id !== CHAT_PANEL_ID);
|
||||
if (mainPanel) {
|
||||
clone.children[1] = { node: mainPanel.node, size: rightChild.size };
|
||||
}
|
||||
return clone;
|
||||
};
|
||||
|
||||
export const Automation = () => {
|
||||
const workspace = useDashboardState<LayoutNode>('screens/automation', defaultLayout);
|
||||
const [selection, setSelection] = usePanelChannel<AutomationSelection>('automation:selected-capability', null);
|
||||
const [selected] = usePanelChannel<TaskSummary | null>('automation:selected-task', null);
|
||||
const isMobile = useIsMobile();
|
||||
const editing = selection?.editing ?? false;
|
||||
const prevEditing = useRef(editing);
|
||||
|
||||
useEffect(() => {
|
||||
if (editing === prevEditing.current) return;
|
||||
prevEditing.current = editing;
|
||||
|
||||
if (editing && !hasChatPanel(workspace.value)) {
|
||||
workspace.setValue(addChatPanel(workspace.value));
|
||||
} else if (!editing && hasChatPanel(workspace.value)) {
|
||||
workspace.setValue(removeChatPanel(workspace.value));
|
||||
}
|
||||
}, [editing]);
|
||||
|
||||
const panelComponents: PanelComponents = useMemo(
|
||||
() => ({
|
||||
'automation-left': AutomationSidebar,
|
||||
'automation-right': AutomationRightPanel,
|
||||
[CHAT_PANEL_ID]: AutomationEditChat,
|
||||
'automation-list': AutomationList,
|
||||
'automation-detail': AutomationDetail,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const mobilePanelId = isMobile && selection ? 'automation-right' : undefined;
|
||||
const onMobileBack = useCallback(() => setSelection(null), [setSelection]);
|
||||
const mobilePanelId = isMobile && selected ? 'automation-detail' : undefined;
|
||||
const onMobileBack = useCallback(() => {}, []);
|
||||
|
||||
if (!workspace.isLoaded) return null;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user