Skip to main content

flow_tui/views/
graph.rs

1use crate::app::App;
2use ratatui::{
3    style::{Modifier, Style},
4    text::{Line, Span},
5    widgets::{Block, Borders, List, ListItem},
6    Frame,
7};
8
9pub fn render(frame: &mut Frame, app: &App) {
10    let area = frame.area();
11    let theme = &app.tui_theme;
12
13    let block = Block::default()
14        .title("Dependency Graph")
15        .borders(Borders::ALL)
16        .border_style(Style::default().fg(theme.border))
17        .title_style(Style::default().fg(theme.primary).add_modifier(Modifier::BOLD));
18
19    let inner = block.inner(area);
20    frame.render_widget(block, area);
21
22    if app.features.is_empty() {
23        return;
24    }
25
26    // Create a simple vertical layout showing features and their dependencies
27    let mut items = Vec::new();
28
29    for feature in &app.features {
30        // Determine status color
31        let status_color = if feature.passes {
32            theme.done
33        } else if feature.in_progress {
34            theme.in_progress
35        } else {
36            theme.pending
37        };
38
39        // Status icon
40        let icon = if feature.passes {
41            "✓"
42        } else if feature.in_progress {
43            "◉"
44        } else {
45            "○"
46        };
47
48        // Check if blocked
49        let is_blocked = !feature.dependencies.is_empty()
50            && feature.dependencies.iter().any(|dep_id| {
51                app.features
52                    .iter()
53                    .find(|f| f.id == *dep_id)
54                    .map(|f| !f.passes)
55                    .unwrap_or(true)
56            });
57
58        let border_color = if is_blocked {
59            theme.blocked
60        } else {
61            status_color
62        };
63
64        // Create feature box
65        items.push(ListItem::new(vec![
66            Line::from(vec![
67                Span::styled("┌─", Style::default().fg(border_color)),
68                Span::styled(
69                    format!(" {} {} ", icon, feature.name),
70                    Style::default().fg(status_color).add_modifier(Modifier::BOLD),
71                ),
72                Span::styled("─┐", Style::default().fg(border_color)),
73            ]),
74            Line::from(vec![
75                Span::styled("│ ", Style::default().fg(border_color)),
76                Span::styled(&feature.category, Style::default().fg(theme.secondary)),
77                Span::styled(" │", Style::default().fg(border_color)),
78            ]),
79            Line::from(vec![Span::styled(
80                "└──────────────────┘",
81                Style::default().fg(border_color),
82            )]),
83        ]));
84
85        // Show dependencies with arrows
86        if !feature.dependencies.is_empty() {
87            for dep_id in &feature.dependencies {
88                if let Some(dep) = app.features.iter().find(|f| f.id == *dep_id) {
89                    let dep_status = if dep.passes {
90                        "✓"
91                    } else if dep.in_progress {
92                        "◉"
93                    } else {
94                        "○"
95                    };
96
97                    items.push(ListItem::new(Line::from(vec![
98                        Span::styled("  ↓ ", Style::default().fg(theme.accent)),
99                        Span::styled(
100                            format!("{} depends on: ", dep_status),
101                            Style::default().fg(theme.muted),
102                        ),
103                        Span::styled(&dep.name, Style::default().fg(theme.foreground)),
104                    ])));
105                }
106            }
107        }
108
109        items.push(ListItem::new(Line::from("")));
110    }
111
112    let list = List::new(items);
113    frame.render_widget(list, inner);
114}