Files
HartOMat/frontend/src/components/workflows/WorkflowListSidebar.tsx
T

170 lines
6.7 KiB
TypeScript

import { GitBranch, Plus, Trash2 } from 'lucide-react'
interface WorkflowListItem {
id: string
name: string
isActive: boolean
presetLabel: string
presetClassName: string
familyLabel: string
familyClassName: string
executionModeLabel: string
executionModeClassName: string
rolloutBadgeLabel: string
rolloutBadgeClassName: string
rolloutStatusLabel: string
rolloutStatusClassName: string
rolloutSummary: string
linkedOutputTypeCount: number
blueprintLabel?: string | null
isReference?: boolean
}
interface WorkflowListSection {
key: string
label: string
className: string
items: WorkflowListItem[]
}
interface WorkflowListSidebarProps {
isLoading: boolean
sections: WorkflowListSection[]
selectedId: string | null
onSelectWorkflow: (workflowId: string) => void
onCreateWorkflow: () => void
onDeleteWorkflow: (workflowId: string, workflowName: string) => void
}
export function WorkflowListSidebar({
isLoading,
sections,
selectedId,
onSelectWorkflow,
onCreateWorkflow,
onDeleteWorkflow,
}: WorkflowListSidebarProps) {
const workflowCount = sections.reduce((count, section) => count + section.items.length, 0)
return (
<aside className="flex w-56 flex-shrink-0 flex-col border-r border-border-default bg-surface">
<div className="flex items-center justify-between border-b border-border-default p-3">
<div className="flex items-center gap-2 text-sm font-semibold text-content-secondary">
<GitBranch size={16} />
Workflows
</div>
<button
onClick={onCreateWorkflow}
className="rounded p-1 text-content-muted hover:bg-surface-hover hover:text-content"
title="New Workflow"
>
<Plus size={16} />
</button>
</div>
<div className="flex-1 space-y-1 overflow-y-auto p-2">
{isLoading && (
<p className="px-2 py-4 text-center text-xs text-content-muted">Loading</p>
)}
{!isLoading && workflowCount === 0 && (
<div className="px-2 py-4 text-center">
<p className="text-xs font-medium text-content-secondary">No workflows configured.</p>
<p className="mt-1 text-xs text-content-muted">
Workflows define the sequence of pipeline steps for rendering orders.
</p>
<button
onClick={onCreateWorkflow}
className="mt-2 text-xs text-accent hover:underline"
>
+ New Workflow
</button>
</div>
)}
{sections.map(section => (
<div key={section.key} className="space-y-1 pb-2">
<div className="flex items-center justify-between px-2 pt-1">
<span className={`rounded-full px-2 py-0.5 text-[11px] font-medium ${section.className}`}>
{section.label}
</span>
<span className="text-xs text-content-muted">{section.items.length}</span>
</div>
{section.items.map(item => (
<div
key={item.id}
role="button"
tabIndex={0}
onClick={() => onSelectWorkflow(item.id)}
onKeyDown={event => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
onSelectWorkflow(item.id)
}
}}
className={`group w-full rounded-lg border px-3 py-2.5 text-left transition-colors focus:outline-none focus:ring-2 focus:ring-accent ${
selectedId === item.id
? 'border-accent/30 bg-accent-light'
: 'border-transparent hover:bg-surface-hover'
}`}
>
<div className="flex items-start justify-between gap-1">
<div className="flex min-w-0 items-center gap-1.5">
{item.isActive && (
<span className="h-2 w-2 flex-shrink-0 rounded-full bg-green-500" title="Active" />
)}
<p className="truncate text-sm font-medium text-content">{item.name}</p>
</div>
<button
type="button"
onClick={event => {
event.stopPropagation()
onDeleteWorkflow(item.id, item.name)
}}
className="flex-shrink-0 rounded p-0.5 text-content-muted opacity-0 hover:bg-red-100 hover:text-red-600 group-hover:opacity-100"
title="Delete"
>
<Trash2 size={12} />
</button>
</div>
<span className={`mt-1 inline-block rounded-full px-1.5 py-0.5 text-xs font-medium ${item.presetClassName}`}>
{item.presetLabel}
</span>
<span className={`ml-1 mt-1 inline-block rounded-full px-1.5 py-0.5 text-xs font-medium ${item.familyClassName}`}>
{item.familyLabel}
</span>
{item.blueprintLabel && (
<span className="ml-1 mt-1 inline-block rounded-full bg-slate-100 px-1.5 py-0.5 text-xs font-medium text-slate-700 dark:bg-slate-900/40 dark:text-slate-300">
{item.blueprintLabel}
</span>
)}
<span className={`ml-1 mt-1 inline-block rounded-full px-1.5 py-0.5 text-xs font-medium ${item.executionModeClassName}`}>
{item.executionModeLabel}
</span>
<span className={`ml-1 mt-1 inline-block rounded-full px-1.5 py-0.5 text-xs font-medium ${item.rolloutBadgeClassName}`}>
{item.rolloutBadgeLabel}
</span>
<span className={`ml-1 mt-1 inline-block rounded-full px-1.5 py-0.5 text-xs font-medium ${item.rolloutStatusClassName}`}>
{item.rolloutStatusLabel}
</span>
<p className="mt-1 text-xs text-content-muted">
{item.linkedOutputTypeCount} linked output type{item.linkedOutputTypeCount === 1 ? '' : 's'}.
</p>
<p className="mt-1 text-xs text-content-muted">
{item.rolloutSummary}
</p>
{item.isReference && (
<p className="mt-1 text-xs text-content-muted">
Canonical reference workflow for parity work.
</p>
)}
{!item.isActive && (
<span className="ml-1 text-xs text-content-muted">(inactive)</span>
)}
</div>
))}
</div>
))}
</div>
</aside>
)
}