diff options
Diffstat (limited to 'frontend/src/components/document/DirectiveFileTree.tsx')
| -rw-r--r-- | frontend/src/components/document/DirectiveFileTree.tsx | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/frontend/src/components/document/DirectiveFileTree.tsx b/frontend/src/components/document/DirectiveFileTree.tsx new file mode 100644 index 0000000..21050ca --- /dev/null +++ b/frontend/src/components/document/DirectiveFileTree.tsx @@ -0,0 +1,154 @@ +import React, { useEffect, useState } from 'react' +import { listDirectives, DirectiveSummary } from '../../services/directiveApi' + +interface DirectiveFileTreeProps { + selectedDirectiveId: string | null + onSelectDirective: (id: string) => void + onNewDirective: () => void +} + +interface GroupState { + [key: string]: boolean +} + +const STATUS_GROUPS = [ + { key: 'active', label: 'Active', defaultExpanded: true }, + { key: 'idle', label: 'Idle', defaultExpanded: true }, + { key: 'draft', label: 'Draft', defaultExpanded: false }, + { key: 'archived', label: 'Archived', defaultExpanded: false }, +] as const + +function statusColor(status: string): string { + switch (status.toLowerCase()) { + case 'active': + case 'running': + return '#4caf50' + case 'idle': + case 'paused': + return '#ffc107' + case 'draft': + case 'pending': + return '#9e9e9e' + case 'archived': + case 'failed': + return '#f44336' + default: + return '#9e9e9e' + } +} + +function groupDirectives(directives: DirectiveSummary[]): Record<string, DirectiveSummary[]> { + const groups: Record<string, DirectiveSummary[]> = { + active: [], + idle: [], + draft: [], + archived: [], + } + + for (const d of directives) { + const s = d.status.toLowerCase() + if (s === 'active' || s === 'running') { + groups.active.push(d) + } else if (s === 'idle' || s === 'paused') { + groups.idle.push(d) + } else if (s === 'draft' || s === 'pending') { + groups.draft.push(d) + } else { + groups.archived.push(d) + } + } + + return groups +} + +export function DirectiveFileTree({ selectedDirectiveId, onSelectDirective, onNewDirective }: DirectiveFileTreeProps) { + const [directives, setDirectives] = useState<DirectiveSummary[]>([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState<string | null>(null) + const [expanded, setExpanded] = useState<GroupState>(() => { + const state: GroupState = {} + for (const g of STATUS_GROUPS) { + state[g.key] = g.defaultExpanded + } + return state + }) + + useEffect(() => { + async function load() { + try { + setLoading(true) + const data = await listDirectives() + setDirectives(data) + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load directives') + } finally { + setLoading(false) + } + } + load() + }, []) + + const toggleGroup = (key: string) => { + setExpanded(prev => ({ ...prev, [key]: !prev[key] })) + } + + const grouped = groupDirectives(directives) + + return ( + <div className="directive-file-tree"> + <div className="file-tree-header"> + <span className="file-tree-title">Directives</span> + <button className="file-tree-new-btn" onClick={onNewDirective} title="New Directive"> + + + </button> + </div> + + {loading && <div className="file-tree-loading">Loading...</div>} + {error && <div className="file-tree-error">{error}</div>} + + {!loading && !error && ( + <div className="file-tree-groups"> + {STATUS_GROUPS.map(group => { + const items = grouped[group.key] + if (!items || items.length === 0) return null + + return ( + <div key={group.key} className="file-tree-group"> + <button + className="file-tree-group-header" + onClick={() => toggleGroup(group.key)} + > + <span className={`file-tree-chevron ${expanded[group.key] ? 'expanded' : ''}`}> + {'\u25B6'} + </span> + <span className="file-tree-group-label">{group.label}</span> + <span className="file-tree-group-count">{items.length}</span> + </button> + + {expanded[group.key] && ( + <div className="file-tree-items"> + {items.map(directive => ( + <button + key={directive.id} + className={`file-tree-item ${selectedDirectiveId === directive.id ? 'selected' : ''}`} + onClick={() => onSelectDirective(directive.id)} + title={directive.title} + > + <span + className="file-tree-status-dot" + style={{ backgroundColor: statusColor(directive.status) }} + /> + <span className="file-tree-doc-icon">{'\u{1F4C4}'}</span> + <span className="file-tree-item-title">{directive.title || 'Untitled'}</span> + </button> + ))} + </div> + )} + </div> + ) + })} + </div> + )} + </div> + ) +} |
