summaryrefslogtreecommitdiff
path: root/frontend/src/components/document/DirectiveFileTree.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/document/DirectiveFileTree.tsx')
-rw-r--r--frontend/src/components/document/DirectiveFileTree.tsx154
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>
+ )
+}