Extract BetriebOverviewPanel, DetailPanel, FlowCanvas, FlowToolbar, StepTable, useFlowGraph hook and helpers into _components/ so page.tsx drops from 1019 to 156 LOC (under the 300 soft target). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
259 lines
7.9 KiB
TypeScript
259 lines
7.9 KiB
TypeScript
'use client'
|
|
|
|
import { useMemo } from 'react'
|
|
import type { Node, Edge } from 'reactflow'
|
|
import { MarkerType } from 'reactflow'
|
|
import { SDK_FLOW_STEPS, FLOW_PACKAGES, type SDKFlowStep } from '../flow-data'
|
|
import {
|
|
NODE_WIDTH,
|
|
PACKAGE_X_OFFSET,
|
|
STEP_Y_START,
|
|
completionColor,
|
|
getStepPosition,
|
|
type PackageFilter,
|
|
} from './helpers'
|
|
|
|
export function useFlowGraph(
|
|
packageFilter: PackageFilter,
|
|
showDb: boolean,
|
|
showRag: boolean,
|
|
selectedStep: SDKFlowStep | null,
|
|
): { nodes: Node[]; edges: Edge[] } {
|
|
return useMemo(() => {
|
|
const nodes: Node[] = []
|
|
const edges: Edge[] = []
|
|
|
|
const visibleSteps =
|
|
packageFilter === 'alle'
|
|
? SDK_FLOW_STEPS
|
|
: SDK_FLOW_STEPS.filter(s => s.package === packageFilter)
|
|
|
|
const visibleStepIds = new Set(visibleSteps.map(s => s.id))
|
|
|
|
// Step Nodes
|
|
visibleSteps.forEach(step => {
|
|
const pkg = FLOW_PACKAGES[step.package]
|
|
const pos = getStepPosition(step)
|
|
const isSelected = selectedStep?.id === step.id
|
|
|
|
// Checkpoint badge text
|
|
let badge = ''
|
|
if (step.checkpointId) {
|
|
badge = step.checkpointType === 'REQUIRED' ? ' *' : ' ~'
|
|
if (step.checkpointReviewer && step.checkpointReviewer !== 'NONE') {
|
|
badge += ` [${step.checkpointReviewer}]`
|
|
}
|
|
}
|
|
|
|
nodes.push({
|
|
id: step.id,
|
|
type: 'default',
|
|
position: pos,
|
|
data: {
|
|
label: (
|
|
<div className="text-center px-1">
|
|
<div className="font-medium text-xs leading-tight">
|
|
{step.nameShort}
|
|
</div>
|
|
<div className="text-[10px] opacity-70 mt-0.5">
|
|
{step.checkpointId || ''}
|
|
{badge}
|
|
</div>
|
|
{step.completion !== undefined && (
|
|
<div className="mt-1.5 px-1">
|
|
<div
|
|
className="flex items-center justify-center gap-1 text-[9px] font-bold mb-0.5"
|
|
style={{ color: isSelected ? 'rgba(255,255,255,0.9)' : completionColor(step.completion) }}
|
|
>
|
|
{step.completion}%
|
|
</div>
|
|
<div
|
|
className="h-1 rounded-full overflow-hidden"
|
|
style={{ background: isSelected ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.1)' }}
|
|
>
|
|
<div
|
|
className="h-full rounded-full"
|
|
style={{
|
|
width: `${step.completion}%`,
|
|
background: isSelected ? 'rgba(255,255,255,0.8)' : completionColor(step.completion),
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
),
|
|
},
|
|
style: {
|
|
background: isSelected ? pkg.color.border : pkg.color.bg,
|
|
color: isSelected ? 'white' : pkg.color.text,
|
|
border: `2px solid ${pkg.color.border}`,
|
|
borderRadius: '10px',
|
|
padding: '8px 4px',
|
|
minWidth: `${NODE_WIDTH}px`,
|
|
maxWidth: `${NODE_WIDTH}px`,
|
|
cursor: 'pointer',
|
|
boxShadow: isSelected
|
|
? `0 0 16px ${pkg.color.border}`
|
|
: '0 1px 3px rgba(0,0,0,0.08)',
|
|
opacity: step.isOptional ? 0.85 : 1,
|
|
borderStyle: step.isOptional ? 'dashed' : 'solid',
|
|
},
|
|
})
|
|
})
|
|
|
|
// Prerequisite Edges
|
|
visibleSteps.forEach(step => {
|
|
step.prerequisiteSteps.forEach(preId => {
|
|
if (visibleStepIds.has(preId)) {
|
|
edges.push({
|
|
id: `e-${preId}-${step.id}`,
|
|
source: preId,
|
|
target: step.id,
|
|
type: 'smoothstep',
|
|
animated: selectedStep?.id === preId || selectedStep?.id === step.id,
|
|
style: {
|
|
stroke:
|
|
selectedStep?.id === preId || selectedStep?.id === step.id
|
|
? '#7c3aed'
|
|
: '#94a3b8',
|
|
strokeWidth:
|
|
selectedStep?.id === preId || selectedStep?.id === step.id
|
|
? 2.5
|
|
: 1.5,
|
|
},
|
|
markerEnd: {
|
|
type: MarkerType.ArrowClosed,
|
|
color:
|
|
selectedStep?.id === preId || selectedStep?.id === step.id
|
|
? '#7c3aed'
|
|
: '#94a3b8',
|
|
width: 14,
|
|
height: 14,
|
|
},
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
// DB Table Nodes
|
|
if (showDb) {
|
|
const dbTablesInUse = new Set<string>()
|
|
visibleSteps.forEach(s => s.dbTables.forEach(t => dbTablesInUse.add(t)))
|
|
|
|
let dbIdx = 0
|
|
dbTablesInUse.forEach(table => {
|
|
const nodeId = `db-${table}`
|
|
nodes.push({
|
|
id: nodeId,
|
|
type: 'default',
|
|
position: { x: -280, y: STEP_Y_START + dbIdx * 90 },
|
|
data: {
|
|
label: (
|
|
<div className="text-center">
|
|
<div className="text-sm mb-0.5">DB</div>
|
|
<div className="font-medium text-[10px] leading-tight">{table}</div>
|
|
</div>
|
|
),
|
|
},
|
|
style: {
|
|
background: '#f1f5f9',
|
|
color: '#475569',
|
|
border: '2px solid #94a3b8',
|
|
borderRadius: '50%',
|
|
width: '90px',
|
|
height: '90px',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
padding: '4px',
|
|
},
|
|
})
|
|
|
|
// Connect steps to this DB table
|
|
visibleSteps
|
|
.filter(s => s.dbTables.includes(table))
|
|
.forEach(step => {
|
|
edges.push({
|
|
id: `e-db-${table}-${step.id}`,
|
|
source: nodeId,
|
|
target: step.id,
|
|
type: 'straight',
|
|
style: {
|
|
stroke: '#94a3b8',
|
|
strokeWidth: 1,
|
|
strokeDasharray: '6 3',
|
|
},
|
|
labelStyle: { fontSize: 9, fill: '#94a3b8' },
|
|
label: step.dbMode !== 'none' ? step.dbMode : undefined,
|
|
})
|
|
})
|
|
|
|
dbIdx++
|
|
})
|
|
}
|
|
|
|
// RAG Collection Nodes
|
|
if (showRag) {
|
|
const ragInUse = new Set<string>()
|
|
visibleSteps.forEach(s => s.ragCollections.forEach(r => ragInUse.add(r)))
|
|
|
|
let ragIdx = 0
|
|
ragInUse.forEach(collection => {
|
|
const nodeId = `rag-${collection}`
|
|
|
|
// Place RAG nodes to the right of all packages
|
|
const rightX = (packageFilter === 'alle' ? 1280 : PACKAGE_X_OFFSET[packageFilter] || 0) + NODE_WIDTH + 180
|
|
nodes.push({
|
|
id: nodeId,
|
|
type: 'default',
|
|
position: { x: rightX, y: STEP_Y_START + ragIdx * 90 },
|
|
data: {
|
|
label: (
|
|
<div className="text-center">
|
|
<div className="text-sm mb-0.5">RAG</div>
|
|
<div className="font-medium text-[10px] leading-tight">
|
|
{collection.replace('bp_', '')}
|
|
</div>
|
|
</div>
|
|
),
|
|
},
|
|
style: {
|
|
background: '#dcfce7',
|
|
color: '#166534',
|
|
border: '2px solid #22c55e',
|
|
borderRadius: '50%',
|
|
width: '90px',
|
|
height: '90px',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
padding: '4px',
|
|
},
|
|
})
|
|
|
|
// Connect steps to this RAG collection
|
|
visibleSteps
|
|
.filter(s => s.ragCollections.includes(collection))
|
|
.forEach(step => {
|
|
edges.push({
|
|
id: `e-rag-${collection}-${step.id}`,
|
|
source: nodeId,
|
|
target: step.id,
|
|
type: 'straight',
|
|
style: {
|
|
stroke: '#22c55e',
|
|
strokeWidth: 1,
|
|
strokeDasharray: '6 3',
|
|
},
|
|
})
|
|
})
|
|
|
|
ragIdx++
|
|
})
|
|
}
|
|
|
|
return { nodes, edges }
|
|
}, [packageFilter, showDb, showRag, selectedStep])
|
|
}
|