Dashboard Components - reza899/AutoSDLC GitHub Wiki

Dashboard Components

#AutoSDLC #UI #Dashboard #Components

← Back to Index | ← UI Architecture

Overview

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.

Component Architecture

Design System

// 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)'
    }
  }
};

Component Structure

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
Loading

Core Dashboard Components

System Overview Dashboard

// 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>
  );
};

Agent Monitor Component

// 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>
  );
};

Agent Card Component

// 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>
  );
};

TDD Status Component

// 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>
  );
};

Workflow Designer Component

// 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>
  );
};

Task Board Component

// 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>
  );
};

Real-time Metrics Chart

// 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>
  );
};

Specialized Components

Agent Communication Visualizer

// 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>
  );
};

Code Coverage Heatmap

// 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>&lt; 60% (Poor)</span>
        </div>
      </div>
    </Card>
  );
};

System Health Monitor

// 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>
  );
};

Utility Components

Notification System

// 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);

Command Palette

// 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>
  );
};

Responsive Design

Mobile Dashboard Layout

// 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>
  );
};

Performance Optimization

Virtual Scrolling for Large Lists

// 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>
  );
};

Lazy Loading Components

// 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>
  );
};

Best Practices

1. Component Design

  • Keep components focused and single-purpose
  • Use composition over inheritance
  • Implement proper error boundaries
  • Follow accessibility guidelines

2. State Management

  • Use local state for UI-specific data
  • Leverage context for cross-component state
  • Implement proper data fetching patterns
  • Cache data appropriately

3. Performance

  • Implement virtual scrolling for large lists
  • Use lazy loading for heavy components
  • Optimize re-renders with memo
  • Monitor bundle size

4. Accessibility

  • Provide proper ARIA labels
  • Ensure keyboard navigation
  • Maintain color contrast ratios
  • Test with screen readers

5. Responsive Design

  • Mobile-first approach
  • Test on various devices
  • Implement touch-friendly interactions
  • Optimize for different screen sizes

Related Documents


Tags: #AutoSDLC #UI #Dashboard #Components #React Last Updated: 2025-06-09 Next: Configuration Interface →

⚠️ **GitHub.com Fallback** ⚠️