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>
143 lines
4.6 KiB
TypeScript
143 lines
4.6 KiB
TypeScript
'use client'
|
|
|
|
import ReactFlow, {
|
|
Node,
|
|
Controls,
|
|
Background,
|
|
MiniMap,
|
|
BackgroundVariant,
|
|
Panel,
|
|
OnNodesChange,
|
|
OnEdgesChange,
|
|
Node as RFNode,
|
|
Edge as RFEdge,
|
|
} from 'reactflow'
|
|
import { SDK_FLOW_STEPS, FLOW_PACKAGES, type SDKFlowStep } from '../flow-data'
|
|
import { DetailPanel } from './DetailPanel'
|
|
import { PACKAGE_ORDER, type PackageFilter } from './helpers'
|
|
|
|
export function FlowCanvas({
|
|
nodes,
|
|
edges,
|
|
onNodesChange,
|
|
onEdgesChange,
|
|
onNodeClick,
|
|
onPaneClick,
|
|
packageFilter,
|
|
selectedStep,
|
|
setSelectedStep,
|
|
}: {
|
|
nodes: RFNode[]
|
|
edges: RFEdge[]
|
|
onNodesChange: OnNodesChange
|
|
onEdgesChange: OnEdgesChange
|
|
onNodeClick: (e: React.MouseEvent, node: Node) => void
|
|
onPaneClick: () => void
|
|
packageFilter: PackageFilter
|
|
selectedStep: SDKFlowStep | null
|
|
setSelectedStep: (s: SDKFlowStep | null) => void
|
|
}) {
|
|
return (
|
|
<div className="flex bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden" style={{ height: '700px' }}>
|
|
{/* Canvas */}
|
|
<div className="flex-1 relative">
|
|
<ReactFlow
|
|
nodes={nodes}
|
|
edges={edges}
|
|
onNodesChange={onNodesChange}
|
|
onEdgesChange={onEdgesChange}
|
|
onNodeClick={onNodeClick}
|
|
onPaneClick={onPaneClick}
|
|
fitView
|
|
fitViewOptions={{ padding: 0.15 }}
|
|
attributionPosition="bottom-left"
|
|
>
|
|
<Controls />
|
|
<MiniMap
|
|
nodeColor={node => {
|
|
if (node.id.startsWith('db-')) return '#94a3b8'
|
|
if (node.id.startsWith('rag-')) return '#22c55e'
|
|
const step = SDK_FLOW_STEPS.find(s => s.id === node.id)
|
|
return step ? FLOW_PACKAGES[step.package].color.border : '#94a3b8'
|
|
}}
|
|
maskColor="rgba(0,0,0,0.08)"
|
|
/>
|
|
<Background variant={BackgroundVariant.Dots} gap={16} size={1} />
|
|
|
|
{/* Legende */}
|
|
<Panel
|
|
position="bottom-right"
|
|
className="bg-white/95 p-3 rounded-lg shadow-lg text-xs"
|
|
>
|
|
<div className="font-medium text-slate-700 mb-2">Legende</div>
|
|
<div className="space-y-1">
|
|
{PACKAGE_ORDER.map(pkgId => {
|
|
const pkg = FLOW_PACKAGES[pkgId]
|
|
return (
|
|
<div key={pkgId} className="flex items-center gap-2">
|
|
<span
|
|
className="w-3 h-3 rounded"
|
|
style={{
|
|
background: pkg.color.bg,
|
|
border: `1px solid ${pkg.color.border}`,
|
|
}}
|
|
/>
|
|
<span className="text-slate-600">{pkg.name}</span>
|
|
</div>
|
|
)
|
|
})}
|
|
<div className="border-t border-slate-200 my-1.5 pt-1.5">
|
|
<div className="flex items-center gap-2">
|
|
<span className="w-3 h-3 rounded-full bg-slate-200 border border-slate-400" />
|
|
<span className="text-slate-500">DB-Tabelle</span>
|
|
</div>
|
|
<div className="flex items-center gap-2 mt-0.5">
|
|
<span className="w-3 h-3 rounded-full bg-green-100 border border-green-500" />
|
|
<span className="text-slate-500">RAG-Collection</span>
|
|
</div>
|
|
</div>
|
|
<div className="border-t border-slate-200 my-1.5 pt-1.5 text-slate-400">
|
|
<div>* = REQUIRED</div>
|
|
<div>~ = RECOMMENDED</div>
|
|
<div>--- = gestrichelte Border: Optional</div>
|
|
</div>
|
|
</div>
|
|
</Panel>
|
|
|
|
{/* Package Headers */}
|
|
{packageFilter === 'alle' && (
|
|
<Panel position="top-center" className="flex gap-2 pointer-events-none">
|
|
{PACKAGE_ORDER.map((pkgId) => {
|
|
const pkg = FLOW_PACKAGES[pkgId]
|
|
return (
|
|
<div
|
|
key={pkgId}
|
|
className="px-3 py-1 rounded-lg text-xs font-medium opacity-60"
|
|
style={{
|
|
background: pkg.color.bg,
|
|
color: pkg.color.text,
|
|
border: `1px solid ${pkg.color.border}`,
|
|
minWidth: '200px',
|
|
textAlign: 'center',
|
|
}}
|
|
>
|
|
{pkg.icon} {pkg.name}
|
|
</div>
|
|
)
|
|
})}
|
|
</Panel>
|
|
)}
|
|
</ReactFlow>
|
|
</div>
|
|
|
|
{/* Detail Panel */}
|
|
{selectedStep && (
|
|
<DetailPanel
|
|
step={selectedStep}
|
|
onClose={() => setSelectedStep(null)}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|