refactor(admin): split sdk-flow page.tsx into colocated components
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>
This commit is contained in:
258
admin-compliance/app/sdk/sdk-flow/_components/useFlowGraph.tsx
Normal file
258
admin-compliance/app/sdk/sdk-flow/_components/useFlowGraph.tsx
Normal file
@@ -0,0 +1,258 @@
|
||||
'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])
|
||||
}
|
||||
Reference in New Issue
Block a user