Dashboard Components - reza899/AutoSDLC GitHub Wiki
#AutoSDLC #UI #Dashboard #Components
← Back to Index | ← UI Architecture
The Dashboard Components document details the UI components that make up the AutoSDLC control interface. These components provide real-time visibility into agent activities, system health, task progress, and enable users to manage and monitor their autonomous development workflows.
// design-system/theme.ts
export const theme = {
colors: {
primary: {
50: '#e3f2fd',
100: '#bbdefb',
200: '#90caf9',
300: '#64b5f6',
400: '#42a5f5',
500: '#2196f3',
600: '#1e88e5',
700: '#1976d2',
800: '#1565c0',
900: '#0d47a1'
},
semantic: {
success: '#4caf50',
warning: '#ff9800',
error: '#f44336',
info: '#2196f3'
},
agent: {
customer: '#9c27b0',
pm: '#3f51b5',
coder: '#009688',
reviewer: '#ff5722',
tester: '#607d8b'
},
tdd: {
red: '#f44336',
green: '#4caf50',
refactor: '#2196f3'
}
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px',
xxl: '48px'
},
typography: {
fontFamily: {
sans: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
mono: 'JetBrains Mono, Consolas, Monaco, "Courier New", monospace'
},
fontSize: {
xs: '12px',
sm: '14px',
base: '16px',
lg: '18px',
xl: '20px',
'2xl': '24px',
'3xl': '30px',
'4xl': '36px'
}
},
shadows: {
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
base: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)'
},
animation: {
duration: {
fast: '150ms',
base: '300ms',
slow: '500ms'
},
easing: {
in: 'cubic-bezier(0.4, 0, 1, 1)',
out: 'cubic-bezier(0, 0, 0.2, 1)',
inOut: 'cubic-bezier(0.4, 0, 0.2, 1)'
}
}
};
graph TB
subgraph "Layout Components"
APP[App Shell]
NAV[Navigation]
SB[Sidebar]
HDR[Header]
end
subgraph "Dashboard Views"
OV[Overview]
AG[Agents Monitor]
WF[Workflow Designer]
TK[Task Board]
MT[Metrics]
end
subgraph "Core Components"
AC[Agent Card]
TC[Task Card]
WC[Workflow Canvas]
CH[Charts]
TB[Tables]
end
subgraph "Utility Components"
MD[Modals]
NT[Notifications]
FRM[Forms]
BTN[Buttons]
ICN[Icons]
end
APP --> NAV & SB & HDR
NAV --> OV & AG & WF & TK & MT
OV --> AC & CH
AG --> AC & TB
WF --> WC
TK --> TC
// components/dashboard/SystemOverview.tsx
import React from 'react';
import { Grid, Card, Metric, Text, Title, BarChart, DonutChart } from '@tremor/react';
import { useSystemMetrics } from '@/hooks/useSystemMetrics';
export const SystemOverview: React.FC = () => {
const { metrics, loading } = useSystemMetrics();
return (
<div className="system-overview">
<header className="dashboard-header">
<Title>System Overview</Title>
<Text>Real-time status of your AutoSDLC deployment</Text>
</header>
<Grid numItems={1} numItemsSm={2} numItemsLg={4} className="gap-6 mt-6">
<MetricCard
title="Active Agents"
metric={metrics?.activeAgents || 0}
delta={metrics?.agentsDelta}
deltaType={metrics?.agentsDelta > 0 ? 'increase' : 'decrease'}
color="blue"
/>
<MetricCard
title="Tasks in Progress"
metric={metrics?.tasksInProgress || 0}
delta={metrics?.tasksDelta}
deltaType="unchanged"
color="amber"
/>
<MetricCard
title="Success Rate"
metric={`${metrics?.successRate || 0}%`}
delta={metrics?.successDelta}
deltaType={metrics?.successDelta > 0 ? 'increase' : 'decrease'}
color="green"
/>
<MetricCard
title="Avg Response Time"
metric={`${metrics?.avgResponseTime || 0}ms`}
delta={metrics?.responseDelta}
deltaType={metrics?.responseDelta < 0 ? 'increase' : 'decrease'}
color="indigo"
/>
</Grid>
<div className="charts-section mt-8">
<Grid numItems={1} numItemsLg={2} className="gap-6">
<Card>
<Title>Task Completion by Agent</Title>
<BarChart
data={metrics?.tasksByAgent || []}
index="agent"
categories={['completed', 'failed']}
colors={['green', 'red']}
className="h-72 mt-4"
/>
</Card>
<Card>
<Title>System Resource Usage</Title>
<DonutChart
data={metrics?.resourceUsage || []}
category="usage"
index="resource"
colors={['blue', 'cyan', 'indigo', 'violet']}
className="h-72 mt-4"
/>
</Card>
</Grid>
</div>
</div>
);
};
// components/dashboard/AgentMonitor.tsx
import React, { useState } from 'react';
import { AgentCard } from './AgentCard';
import { AgentFilters } from './AgentFilters';
import { useAgents } from '@/hooks/useAgents';
export const AgentMonitor: React.FC = () => {
const [filters, setFilters] = useState<AgentFilters>({});
const { agents, loading, error } = useAgents(filters);
return (
<div className="agent-monitor">
<div className="monitor-header">
<h2 className="text-2xl font-bold">Agent Monitor</h2>
<AgentFilters value={filters} onChange={setFilters} />
</div>
<div className="agent-grid grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 mt-6">
{agents.map(agent => (
<AgentCard key={agent.id} agent={agent} />
))}
</div>
{agents.length === 0 && !loading && (
<EmptyState
title="No agents found"
description="Try adjusting your filters or deploy a new agent"
action={{
label: "Deploy Agent",
onClick: () => openDeployModal()
}}
/>
)}
</div>
);
};
// components/dashboard/AgentCard.tsx
import React from 'react';
import { Card, Badge, Metric, Text, Flex, ProgressBar } from '@tremor/react';
import { Activity, AlertCircle, CheckCircle, Clock } from 'lucide-react';
import { Agent, AgentStatus } from '@/types';
interface AgentCardProps {
agent: Agent;
onSelect?: (agent: Agent) => void;
}
export const AgentCard: React.FC<AgentCardProps> = ({ agent, onSelect }) => {
const statusIcon = {
active: <Activity className="w-4 h-4 text-green-500" />,
busy: <Clock className="w-4 h-4 text-yellow-500" />,
error: <AlertCircle className="w-4 h-4 text-red-500" />,
idle: <CheckCircle className="w-4 h-4 text-gray-500" />
};
const statusColor = {
active: 'green',
busy: 'yellow',
error: 'red',
idle: 'gray'
};
return (
<Card
className="agent-card cursor-pointer hover:shadow-lg transition-shadow"
onClick={() => onSelect?.(agent)}
>
<Flex justifyContent="between" alignItems="start">
<div>
<Flex alignItems="center" className="gap-2">
<div className={`agent-icon agent-${agent.type}`}>
{agent.type.charAt(0).toUpperCase()}
</div>
<div>
<Text className="font-medium">{agent.name}</Text>
<Text className="text-xs text-gray-500">{agent.id}</Text>
</div>
</Flex>
</div>
<Badge color={statusColor[agent.status]} icon={statusIcon[agent.status]}>
{agent.status}
</Badge>
</Flex>
{agent.currentTask && (
<div className="current-task mt-4">
<Text className="text-sm font-medium">Current Task</Text>
<Text className="text-sm text-gray-600 truncate">{agent.currentTask.name}</Text>
<ProgressBar
value={agent.currentTask.progress}
className="mt-2"
color="blue"
/>
</div>
)}
<div className="agent-metrics mt-4 grid grid-cols-2 gap-4">
<div>
<Text className="text-xs text-gray-500">Tasks Today</Text>
<Metric className="text-lg">{agent.metrics.tasksCompleted}</Metric>
</div>
<div>
<Text className="text-xs text-gray-500">Success Rate</Text>
<Metric className="text-lg">{agent.metrics.successRate}%</Metric>
</div>
</div>
{agent.status === 'error' && agent.lastError && (
<div className="error-info mt-4 p-2 bg-red-50 rounded">
<Text className="text-xs text-red-600">{agent.lastError}</Text>
</div>
)}
<div className="agent-actions mt-4 flex gap-2">
<button className="text-xs text-blue-600 hover:text-blue-800">
View Logs
</button>
<button className="text-xs text-blue-600 hover:text-blue-800">
View Output
</button>
<button className="text-xs text-blue-600 hover:text-blue-800">
Restart
</button>
</div>
</Card>
);
};
// components/dashboard/TDDStatus.tsx
import React from 'react';
import { Card, Title, Badge, ProgressBar, Text } from '@tremor/react';
import { CheckCircle, XCircle, RefreshCw } from 'lucide-react';
interface TDDStatusProps {
taskId: string;
phase: 'red' | 'green' | 'refactor';
testResults: {
total: number;
passing: number;
failing: number;
coverage: number;
};
}
export const TDDStatus: React.FC<TDDStatusProps> = ({ taskId, phase, testResults }) => {
const phaseConfig = {
red: {
color: 'red',
icon: <XCircle className="w-4 h-4" />,
label: 'Red Phase',
description: 'Writing failing tests'
},
green: {
color: 'green',
icon: <CheckCircle className="w-4 h-4" />,
label: 'Green Phase',
description: 'Making tests pass'
},
refactor: {
color: 'blue',
icon: <RefreshCw className="w-4 h-4" />,
label: 'Refactor Phase',
description: 'Improving code quality'
}
};
const config = phaseConfig[phase];
const testPassRate = (testResults.passing / testResults.total) * 100;
return (
<Card className="tdd-status">
<div className="flex justify-between items-start mb-4">
<div>
<Title>TDD Status</Title>
<Text className="text-sm text-gray-500">Task: {taskId}</Text>
</div>
<Badge color={config.color} icon={config.icon}>
{config.label}
</Badge>
</div>
<Text className="text-sm mb-4">{config.description}</Text>
<div className="test-results space-y-4">
<div>
<div className="flex justify-between mb-1">
<Text className="text-sm">Test Progress</Text>
<Text className="text-sm font-medium">
{testResults.passing}/{testResults.total} passing
</Text>
</div>
<ProgressBar
value={testPassRate}
color={phase === 'red' ? 'red' : 'green'}
/>
</div>
<div>
<div className="flex justify-between mb-1">
<Text className="text-sm">Code Coverage</Text>
<Text className="text-sm font-medium">{testResults.coverage}%</Text>
</div>
<ProgressBar
value={testResults.coverage}
color={testResults.coverage >= 80 ? 'green' : 'yellow'}
/>
</div>
</div>
{phase === 'red' && testResults.passing > 0 && (
<div className="mt-4 p-3 bg-yellow-50 rounded">
<Text className="text-sm text-yellow-800">
⚠️ Some tests are passing in red phase. Ensure all tests fail before implementation.
</Text>
</div>
)}
</Card>
);
};
// components/dashboard/WorkflowDesigner.tsx
import React, { useCallback } from 'react';
import ReactFlow, {
Node,
Edge,
Controls,
Background,
MiniMap,
addEdge,
useNodesState,
useEdgesState,
ReactFlowProvider
} from 'reactflow';
import { WorkflowNodeTypes } from './WorkflowNodes';
import { WorkflowSidebar } from './WorkflowSidebar';
import 'reactflow/dist/style.css';
export const WorkflowDesigner: React.FC = () => {
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge(params, eds)),
[setEdges]
);
const onDrop = useCallback(
(event) => {
event.preventDefault();
const type = event.dataTransfer.getData('nodeType');
const position = { x: event.clientX - 200, y: event.clientY - 100 };
const newNode: Node = {
id: `${type}-${Date.now()}`,
type,
position,
data: { label: `${type} Node` }
};
setNodes((nds) => nds.concat(newNode));
},
[setNodes]
);
return (
<div className="workflow-designer h-full flex">
<WorkflowSidebar />
<div className="flex-1 relative">
<ReactFlowProvider>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onDrop={onDrop}
onDragOver={(e) => e.preventDefault()}
nodeTypes={WorkflowNodeTypes}
fitView
>
<Background variant="dots" gap={12} size={1} />
<Controls />
<MiniMap
nodeColor={(node) => {
switch (node.type) {
case 'trigger': return '#9c27b0';
case 'agent': return '#2196f3';
case 'condition': return '#ff9800';
case 'action': return '#4caf50';
default: return '#607d8b';
}
}}
/>
</ReactFlow>
</ReactFlowProvider>
</div>
</div>
);
};
// components/dashboard/TaskBoard.tsx
import React from 'react';
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
import { TaskCard } from './TaskCard';
import { useTaskBoard } from '@/hooks/useTaskBoard';
const columns = [
{ id: 'pending', title: 'Pending', color: 'gray' },
{ id: 'in_progress', title: 'In Progress', color: 'blue' },
{ id: 'testing', title: 'Testing', color: 'yellow' },
{ id: 'review', title: 'Review', color: 'purple' },
{ id: 'completed', title: 'Completed', color: 'green' }
];
export const TaskBoard: React.FC = () => {
const { tasks, moveTask, loading } = useTaskBoard();
const handleDragEnd = (result) => {
if (!result.destination) return;
moveTask(
result.draggableId,
result.source.droppableId,
result.destination.droppableId,
result.source.index,
result.destination.index
);
};
return (
<div className="task-board">
<div className="board-header mb-6">
<h2 className="text-2xl font-bold">Task Board</h2>
<div className="board-actions flex gap-2">
<button className="btn-primary">New Task</button>
<button className="btn-secondary">Filter</button>
</div>
</div>
<DragDropContext onDragEnd={handleDragEnd}>
<div className="board-columns flex gap-4 overflow-x-auto">
{columns.map(column => (
<div key={column.id} className="board-column flex-1 min-w-[300px]">
<div className={`column-header p-3 bg-${column.color}-100 rounded-t`}>
<h3 className="font-medium">{column.title}</h3>
<span className="text-sm text-gray-600">
{tasks[column.id]?.length || 0} tasks
</span>
</div>
<Droppable droppableId={column.id}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.droppableProps}
className={`column-content p-2 min-h-[400px] bg-gray-50 ${
snapshot.isDraggingOver ? 'bg-blue-50' : ''
}`}
>
{tasks[column.id]?.map((task, index) => (
<Draggable key={task.id} draggableId={task.id} index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className={`mb-2 ${snapshot.isDragging ? 'opacity-50' : ''}`}
>
<TaskCard task={task} />
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</div>
))}
</div>
</DragDropContext>
</div>
);
};
// components/dashboard/RealTimeMetrics.tsx
import React, { useEffect, useRef } from 'react';
import { Line } from 'react-chartjs-2';
import { useWebSocket } from '@/hooks/useWebSocket';
export const RealTimeMetrics: React.FC = () => {
const chartRef = useRef(null);
const { lastMessage } = useWebSocket('/ws');
const [data, setData] = useState({
labels: [],
datasets: [
{
label: 'Response Time (ms)',
data: [],
borderColor: 'rgb(33, 150, 243)',
backgroundColor: 'rgba(33, 150, 243, 0.1)',
tension: 0.4
},
{
label: 'Active Tasks',
data: [],
borderColor: 'rgb(76, 175, 80)',
backgroundColor: 'rgba(76, 175, 80, 0.1)',
tension: 0.4,
yAxisID: 'y1'
}
]
});
useEffect(() => {
if (lastMessage?.type === 'system_metrics') {
setData(prevData => {
const newLabels = [...prevData.labels, new Date().toLocaleTimeString()];
const newResponseTimes = [...prevData.datasets[0].data, lastMessage.data.responseTime];
const newActiveTasks = [...prevData.datasets[1].data, lastMessage.data.activeTasks];
// Keep only last 20 data points
if (newLabels.length > 20) {
newLabels.shift();
newResponseTimes.shift();
newActiveTasks.shift();
}
return {
labels: newLabels,
datasets: [
{ ...prevData.datasets[0], data: newResponseTimes },
{ ...prevData.datasets[1], data: newActiveTasks }
]
};
});
}
}, [lastMessage]);
const options = {
responsive: true,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: 'Real-time System Metrics'
}
},
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: 'Response Time (ms)'
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: {
display: true,
text: 'Active Tasks'
},
grid: {
drawOnChartArea: false,
},
},
},
};
return (
<div className="real-time-metrics">
<Line ref={chartRef} data={data} options={options} />
</div>
);
};
// components/dashboard/AgentCommunicationVisualizer.tsx
import React, { useEffect, useRef } from 'react';
import * as d3 from 'd3';
import { useAgentCommunication } from '@/hooks/useAgentCommunication';
export const AgentCommunicationVisualizer: React.FC = () => {
const svgRef = useRef<SVGSVGElement>(null);
const { communications } = useAgentCommunication();
useEffect(() => {
if (!svgRef.current || !communications.length) return;
const svg = d3.select(svgRef.current);
const width = svgRef.current.clientWidth;
const height = svgRef.current.clientHeight;
// Clear previous content
svg.selectAll('*').remove();
// Create force simulation
const simulation = d3.forceSimulation()
.force('link', d3.forceLink().id(d => d.id).distance(100))
.force('charge', d3.forceManyBody().strength(-300))
.force('center', d3.forceCenter(width / 2, height / 2));
// Process data
const nodes = Array.from(new Set(
communications.flatMap(c => [c.source, c.target])
)).map(id => ({ id }));
const links = communications.map(c => ({
source: c.source,
target: c.target,
type: c.type,
count: c.count
}));
// Add arrow markers
svg.append('defs').selectAll('marker')
.data(['end'])
.enter().append('marker')
.attr('id', String)
.attr('viewBox', '0 -5 10 10')
.attr('refX', 25)
.attr('refY', 0)
.attr('markerWidth', 8)
.attr('markerHeight', 8)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#999');
// Add links
const link = svg.append('g')
.selectAll('line')
.data(links)
.enter().append('line')
.attr('stroke', '#999')
.attr('stroke-opacity', 0.6)
.attr('stroke-width', d => Math.sqrt(d.count))
.attr('marker-end', 'url(#end)');
// Add nodes
const node = svg.append('g')
.selectAll('circle')
.data(nodes)
.enter().append('circle')
.attr('r', 20)
.attr('fill', d => theme.colors.agent[d.id.split('-')[0]] || '#999')
.call(d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended));
// Add labels
const label = svg.append('g')
.selectAll('text')
.data(nodes)
.enter().append('text')
.text(d => d.id)
.attr('font-size', 12)
.attr('text-anchor', 'middle')
.attr('dy', 4);
// Update positions on tick
simulation
.nodes(nodes)
.on('tick', ticked);
simulation.force('link')
.links(links);
function ticked() {
link
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
node
.attr('cx', d => d.x)
.attr('cy', d => d.y);
label
.attr('x', d => d.x)
.attr('y', d => d.y);
}
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}, [communications]);
return (
<div className="agent-communication-visualizer h-full">
<svg ref={svgRef} className="w-full h-full" />
</div>
);
};
// components/dashboard/CodeCoverageHeatmap.tsx
import React from 'react';
import { Card, Title } from '@tremor/react';
import { HeatMapGrid } from 'react-grid-heatmap';
interface CoverageData {
module: string;
files: {
name: string;
coverage: number;
}[];
}
export const CodeCoverageHeatmap: React.FC<{ data: CoverageData[] }> = ({ data }) => {
const xLabels = data.map(d => d.module);
const yLabels = Array.from(new Set(data.flatMap(d => d.files.map(f => f.name))));
const heatmapData = yLabels.map(file =>
xLabels.map(module => {
const moduleData = data.find(d => d.module === module);
const fileData = moduleData?.files.find(f => f.name === file);
return fileData?.coverage || 0;
})
);
return (
<Card className="code-coverage-heatmap">
<Title>Code Coverage by Module</Title>
<div className="mt-4">
<HeatMapGrid
data={heatmapData}
xLabels={xLabels}
yLabels={yLabels}
cellRender={(x, y, value) => (
<div title={`${value}%`}>{value}</div>
)}
xLabelsStyle={() => ({
fontSize: '12px',
textTransform: 'uppercase',
color: '#777'
})}
yLabelsStyle={() => ({
fontSize: '12px',
color: '#777'
})}
cellStyle={(x, y, value) => ({
background: value >= 80 ? '#4caf50' :
value >= 60 ? '#ff9800' : '#f44336',
fontSize: '11px',
color: '#fff',
border: '1px solid #fff'
})}
cellHeight="30px"
xLabelsPos="top"
yLabelsPos="left"
square
/>
</div>
<div className="coverage-legend mt-4 flex gap-4 text-sm">
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-green-500"></div>
<span>≥ 80% (Good)</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-orange-500"></div>
<span>60-79% (Fair)</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-red-500"></div>
<span>< 60% (Poor)</span>
</div>
</div>
</Card>
);
};
// components/dashboard/SystemHealthMonitor.tsx
import React from 'react';
import { Card, Grid, Metric, Text, BadgeDelta, CategoryBar } from '@tremor/react';
import { Activity, Database, Cpu, HardDrive } from 'lucide-react';
export const SystemHealthMonitor: React.FC = () => {
const { health } = useSystemHealth();
const getHealthColor = (score: number) => {
if (score >= 90) return 'green';
if (score >= 70) return 'yellow';
return 'red';
};
return (
<div className="system-health-monitor">
<Grid numItems={1} numItemsLg={2} className="gap-6">
<Card>
<div className="flex items-center justify-between">
<div>
<Text>Overall System Health</Text>
<Metric className="mt-2">{health.overall}%</Metric>
</div>
<div className={`p-3 rounded-full bg-${getHealthColor(health.overall)}-100`}>
<Activity className={`w-8 h-8 text-${getHealthColor(health.overall)}-600`} />
</div>
</div>
<CategoryBar
values={[health.overall]}
colors={[getHealthColor(health.overall)]}
className="mt-4"
/>
</Card>
<Grid numItems={2} className="gap-4">
<HealthMetricCard
icon={<Cpu />}
label="CPU Usage"
value={health.cpu}
unit="%"
threshold={80}
/>
<HealthMetricCard
icon={<HardDrive />}
label="Memory"
value={health.memory}
unit="GB"
max={16}
/>
<HealthMetricCard
icon={<Database />}
label="Database"
value={health.database}
unit="ms"
threshold={100}
inverse
/>
<HealthMetricCard
icon={<Activity />}
label="API Latency"
value={health.apiLatency}
unit="ms"
threshold={200}
inverse
/>
</Grid>
</Grid>
{health.issues.length > 0 && (
<Card className="mt-6">
<Text className="font-medium mb-3">Active Issues</Text>
<div className="space-y-2">
{health.issues.map((issue, idx) => (
<div key={idx} className="flex items-center justify-between p-2 bg-yellow-50 rounded">
<Text className="text-sm">{issue.description}</Text>
<BadgeDelta deltaType="decrease" size="xs">
{issue.impact}
</BadgeDelta>
</div>
))}
</div>
</Card>
)}
</div>
);
};
// components/notifications/NotificationProvider.tsx
import React, { createContext, useContext, useState } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import { X, CheckCircle, AlertCircle, Info, AlertTriangle } from 'lucide-react';
interface Notification {
id: string;
type: 'success' | 'error' | 'warning' | 'info';
title: string;
message?: string;
duration?: number;
actions?: NotificationAction[];
}
const NotificationContext = createContext<{
notify: (notification: Omit<Notification, 'id'>) => void;
dismiss: (id: string) => void;
}>({
notify: () => {},
dismiss: () => {}
});
export const NotificationProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [notifications, setNotifications] = useState<Notification[]>([]);
const notify = (notification: Omit<Notification, 'id'>) => {
const id = Date.now().toString();
const newNotification = { ...notification, id };
setNotifications(prev => [...prev, newNotification]);
if (notification.duration !== 0) {
setTimeout(() => {
dismiss(id);
}, notification.duration || 5000);
}
};
const dismiss = (id: string) => {
setNotifications(prev => prev.filter(n => n.id !== id));
};
const icons = {
success: <CheckCircle className="w-5 h-5 text-green-500" />,
error: <AlertCircle className="w-5 h-5 text-red-500" />,
warning: <AlertTriangle className="w-5 h-5 text-yellow-500" />,
info: <Info className="w-5 h-5 text-blue-500" />
};
return (
<NotificationContext.Provider value={{ notify, dismiss }}>
{children}
<div className="fixed top-4 right-4 z-50 space-y-2">
<AnimatePresence>
{notifications.map(notification => (
<motion.div
key={notification.id}
initial={{ opacity: 0, x: 50 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 50 }}
className="bg-white rounded-lg shadow-lg p-4 min-w-[320px] max-w-[420px]"
>
<div className="flex items-start gap-3">
{icons[notification.type]}
<div className="flex-1">
<h4 className="font-medium">{notification.title}</h4>
{notification.message && (
<p className="text-sm text-gray-600 mt-1">{notification.message}</p>
)}
{notification.actions && (
<div className="flex gap-2 mt-3">
{notification.actions.map((action, idx) => (
<button
key={idx}
onClick={() => {
action.onClick();
dismiss(notification.id);
}}
className="text-sm font-medium text-blue-600 hover:text-blue-800"
>
{action.label}
</button>
))}
</div>
)}
</div>
<button
onClick={() => dismiss(notification.id)}
className="text-gray-400 hover:text-gray-600"
>
<X className="w-4 h-4" />
</button>
</div>
</motion.div>
))}
</AnimatePresence>
</div>
</NotificationContext.Provider>
);
};
export const useNotification = () => useContext(NotificationContext);
// components/CommandPalette.tsx
import React, { useState, useEffect } from 'react';
import { Command } from 'cmdk';
import { Search, FileText, Users, Settings, Play } from 'lucide-react';
export const CommandPalette: React.FC = () => {
const [open, setOpen] = useState(false);
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((open) => !open);
}
};
document.addEventListener('keydown', down);
return () => document.removeEventListener('keydown', down);
}, []);
return (
<Command.Dialog
open={open}
onOpenChange={setOpen}
className="fixed inset-0 z-50 flex items-start justify-center pt-20"
>
<div className="bg-white rounded-lg shadow-2xl w-full max-w-2xl overflow-hidden">
<Command.Input
placeholder="Type a command or search..."
className="w-full px-4 py-3 text-lg border-b"
/>
<Command.List className="max-h-96 overflow-y-auto p-2">
<Command.Empty className="p-4 text-center text-gray-500">
No results found.
</Command.Empty>
<Command.Group heading="Actions" className="p-2">
<Command.Item
onSelect={() => {
setOpen(false);
// Deploy new agent
}}
className="flex items-center gap-3 px-3 py-2 rounded hover:bg-gray-100 cursor-pointer"
>
<Play className="w-4 h-4" />
<span>Deploy New Agent</span>
</Command.Item>
<Command.Item
onSelect={() => {
setOpen(false);
// Create workflow
}}
className="flex items-center gap-3 px-3 py-2 rounded hover:bg-gray-100 cursor-pointer"
>
<FileText className="w-4 h-4" />
<span>Create Workflow</span>
</Command.Item>
</Command.Group>
<Command.Group heading="Navigation" className="p-2">
<Command.Item
onSelect={() => {
setOpen(false);
// Navigate to agents
}}
className="flex items-center gap-3 px-3 py-2 rounded hover:bg-gray-100 cursor-pointer"
>
<Users className="w-4 h-4" />
<span>Agent Monitor</span>
</Command.Item>
<Command.Item
onSelect={() => {
setOpen(false);
// Navigate to settings
}}
className="flex items-center gap-3 px-3 py-2 rounded hover:bg-gray-100 cursor-pointer"
>
<Settings className="w-4 h-4" />
<span>System Settings</span>
</Command.Item>
</Command.Group>
</Command.List>
</div>
</Command.Dialog>
);
};
// components/dashboard/MobileDashboard.tsx
import React, { useState } from 'react';
import { Menu, X } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
export const MobileDashboard: React.FC = () => {
const [menuOpen, setMenuOpen] = useState(false);
const [activeView, setActiveView] = useState('overview');
const views = {
overview: <MobileOverview />,
agents: <MobileAgentList />,
tasks: <MobileTaskList />,
metrics: <MobileMetrics />
};
return (
<div className="mobile-dashboard min-h-screen bg-gray-50">
<header className="bg-white shadow-sm px-4 py-3 flex items-center justify-between">
<h1 className="text-lg font-semibold">AutoSDLC</h1>
<button onClick={() => setMenuOpen(!menuOpen)}>
{menuOpen ? <X /> : <Menu />}
</button>
</header>
<AnimatePresence>
{menuOpen && (
<motion.nav
initial={{ x: '100%' }}
animate={{ x: 0 }}
exit={{ x: '100%' }}
className="fixed inset-y-0 right-0 w-64 bg-white shadow-lg z-40"
>
<div className="p-4">
<h2 className="text-sm font-medium text-gray-500 mb-4">Navigation</h2>
{Object.keys(views).map(view => (
<button
key={view}
onClick={() => {
setActiveView(view);
setMenuOpen(false);
}}
className={`block w-full text-left px-3 py-2 rounded ${
activeView === view ? 'bg-blue-50 text-blue-600' : ''
}`}
>
{view.charAt(0).toUpperCase() + view.slice(1)}
</button>
))}
</div>
</motion.nav>
)}
</AnimatePresence>
<main className="p-4">
{views[activeView]}
</main>
<nav className="fixed bottom-0 left-0 right-0 bg-white border-t">
<div className="grid grid-cols-4 py-2">
{Object.keys(views).map(view => (
<button
key={view}
onClick={() => setActiveView(view)}
className={`py-2 text-xs ${
activeView === view ? 'text-blue-600' : 'text-gray-500'
}`}
>
{view.charAt(0).toUpperCase() + view.slice(1)}
</button>
))}
</div>
</nav>
</div>
);
};
// components/VirtualizedAgentList.tsx
import React from 'react';
import { FixedSizeList } from 'react-window';
import { AgentCard } from './AgentCard';
export const VirtualizedAgentList: React.FC<{ agents: Agent[] }> = ({ agents }) => {
const Row = ({ index, style }) => (
<div style={style}>
<AgentCard agent={agents[index]} />
</div>
);
return (
<FixedSizeList
height={600}
itemCount={agents.length}
itemSize={200}
width="100%"
>
{Row}
</FixedSizeList>
);
};
// components/LazyDashboard.tsx
import React, { lazy, Suspense } from 'react';
import { Skeleton } from './Skeleton';
const WorkflowDesigner = lazy(() => import('./WorkflowDesigner'));
const MetricsDashboard = lazy(() => import('./MetricsDashboard'));
export const LazyDashboard: React.FC = () => {
return (
<div className="dashboard">
<Suspense fallback={<Skeleton height={400} />}>
<WorkflowDesigner />
</Suspense>
<Suspense fallback={<Skeleton height={300} />}>
<MetricsDashboard />
</Suspense>
</div>
);
};
- Keep components focused and single-purpose
- Use composition over inheritance
- Implement proper error boundaries
- Follow accessibility guidelines
- Use local state for UI-specific data
- Leverage context for cross-component state
- Implement proper data fetching patterns
- Cache data appropriately
- Implement virtual scrolling for large lists
- Use lazy loading for heavy components
- Optimize re-renders with memo
- Monitor bundle size
- Provide proper ARIA labels
- Ensure keyboard navigation
- Maintain color contrast ratios
- Test with screen readers
- Mobile-first approach
- Test on various devices
- Implement touch-friendly interactions
- Optimize for different screen sizes
Tags: #AutoSDLC #UI #Dashboard #Components #React Last Updated: 2025-06-09 Next: Configuration Interface →