File size: 5,166 Bytes
5a81b95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/**
 * ╔═══════════════════════════════════════════════════════════════════════════╗
 * β•‘                    FLOWCHART VIEW COMPONENT                               β•‘
 * ║═══════════════════════════════════════════════════════════════════════════║
 * β•‘  Business process and decision flow visualization                         β•‘
 * β•‘  Part of the Visual Cortex Layer                                         β•‘
 * β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
 */

import { useMemo } from 'react';
import { MermaidDiagram } from './MermaidDiagram';
import { cn } from '@/lib/utils';

export interface FlowNode {
  id: string;
  label: string;
  type?: 'start' | 'end' | 'process' | 'decision' | 'data' | 'subprocess' | 'custom';
  style?: string;
}

export interface FlowEdge {
  from: string;
  to: string;
  label?: string;
  style?: 'solid' | 'dashed' | 'thick';
}

export interface FlowSubgraph {
  id: string;
  label: string;
  nodes: string[];
  direction?: 'TB' | 'LR';
}

export interface FlowchartViewProps {
  /** Flowchart title */
  title?: string;
  /** Nodes in the flowchart */
  nodes: FlowNode[];
  /** Edges connecting nodes */
  edges: FlowEdge[];
  /** Optional subgraphs for grouping */
  subgraphs?: FlowSubgraph[];
  /** Flow direction: TB (top-bottom), LR (left-right), BT, RL */
  direction?: 'TB' | 'LR' | 'BT' | 'RL';
  /** Custom class */
  className?: string;
  /** Callback when rendered */
  onRender?: (svg: string) => void;
}

// Node type to Mermaid shape
const NODE_SHAPES: Record<string, { prefix: string; suffix: string }> = {
  start: { prefix: '([', suffix: '])' },        // Stadium (rounded)
  end: { prefix: '([', suffix: '])' },          // Stadium
  process: { prefix: '[', suffix: ']' },        // Rectangle
  decision: { prefix: '{', suffix: '}' },       // Diamond
  data: { prefix: '[/', suffix: '/]' },         // Parallelogram
  subprocess: { prefix: '[[', suffix: ']]' },   // Subroutine
  custom: { prefix: '[', suffix: ']' },         // Rectangle
};

// Edge style to arrow
const EDGE_STYLES: Record<string, string> = {
  solid: '-->',
  dashed: '-.->',
  thick: '==>',
};

export function FlowchartView({
  title,
  nodes,
  edges,
  subgraphs = [],
  direction = 'TB',
  className,
  onRender,
}: FlowchartViewProps) {
  const mermaidCode = useMemo(() => {
    const lines: string[] = [`flowchart ${direction}`];

    // Track nodes in subgraphs
    const nodesInSubgraphs = new Set<string>();
    subgraphs.forEach(sg => sg.nodes.forEach(n => nodesInSubgraphs.add(n)));

    // Generate subgraphs
    subgraphs.forEach(sg => {
      const subDir = sg.direction ? ` direction ${sg.direction}` : '';
      lines.push(`  subgraph ${sg.id}["${sg.label}"]${subDir}`);

      sg.nodes.forEach(nodeId => {
        const node = nodes.find(n => n.id === nodeId);
        if (node) {
          const shape = NODE_SHAPES[node.type || 'process'];
          lines.push(`    ${node.id}${shape.prefix}"${node.label}"${shape.suffix}`);
        }
      });

      lines.push('  end');
    });

    // Generate standalone nodes
    nodes.forEach(node => {
      if (!nodesInSubgraphs.has(node.id)) {
        const shape = NODE_SHAPES[node.type || 'process'];
        lines.push(`  ${node.id}${shape.prefix}"${node.label}"${shape.suffix}`);
      }
    });

    // Generate edges
    edges.forEach(edge => {
      const arrow = EDGE_STYLES[edge.style || 'solid'];
      if (edge.label) {
        lines.push(`  ${edge.from} ${arrow}|"${edge.label}"| ${edge.to}`);
      } else {
        lines.push(`  ${edge.from} ${arrow} ${edge.to}`);
      }
    });

    // Add styling
    lines.push('');
    lines.push('  %% Styling');

    // Style start/end nodes
    const startNodes = nodes.filter(n => n.type === 'start').map(n => n.id);
    const endNodes = nodes.filter(n => n.type === 'end').map(n => n.id);
    const decisionNodes = nodes.filter(n => n.type === 'decision').map(n => n.id);

    if (startNodes.length > 0) {
      lines.push(`  style ${startNodes.join(',')} fill:#22c55e,stroke:#16a34a,color:#fff`);
    }
    if (endNodes.length > 0) {
      lines.push(`  style ${endNodes.join(',')} fill:#ef4444,stroke:#dc2626,color:#fff`);
    }
    if (decisionNodes.length > 0) {
      lines.push(`  style ${decisionNodes.join(',')} fill:#f59e0b,stroke:#d97706,color:#000`);
    }

    return lines.join('\n');
  }, [nodes, edges, subgraphs, direction]);

  return (
    <div className={cn('flowchart-view', className)}>
      <MermaidDiagram
        code={mermaidCode}
        title={title}
        theme="dark"
        zoomable={true}
        onRender={onRender}
      />
    </div>
  );
}

export default FlowchartView;