Skip to main content

flow_tui/views/
agents.rs

1use crate::app::App;
2use ratatui::{
3    layout::{Alignment, Constraint, Direction, Layout},
4    style::{Modifier, Style},
5    text::{Line, Span},
6    widgets::{Block, Borders, Gauge, Paragraph},
7    Frame,
8};
9
10#[allow(clippy::too_many_lines)]
11pub fn render(frame: &mut Frame, app: &App) {
12    let area = frame.area();
13    let theme = &app.tui_theme;
14
15    // Split main area into dashboard and footer
16    let chunks_outer = Layout::default()
17        .direction(Direction::Vertical)
18        .constraints([
19            Constraint::Min(0),    // Dashboard
20            Constraint::Length(1), // Footer
21        ])
22        .split(area);
23
24    let dashboard_area = chunks_outer[0];
25    let footer_area = chunks_outer[1];
26
27    let block = Block::default()
28        .title("Agent Dashboard")
29        .borders(Borders::ALL)
30        .border_style(Style::default().fg(theme.border))
31        .title_style(
32            Style::default()
33                .fg(theme.primary)
34                .add_modifier(Modifier::BOLD),
35        );
36
37    let inner = block.inner(dashboard_area);
38    frame.render_widget(block, dashboard_area);
39
40    // Use real stats from database if available, otherwise calculate from features
41    let (total, passing, in_progress, _pending) = if let Some(ref stats) = app.feature_stats {
42        (stats.total, stats.passing, stats.in_progress, stats.failing)
43    } else {
44        let total = app.features.len();
45        let passing = app.features.iter().filter(|f| f.passes).count();
46        let in_progress = app.features.iter().filter(|f| f.in_progress).count();
47        let pending = app
48            .features
49            .iter()
50            .filter(|f| !f.passes && !f.in_progress)
51            .count();
52        (total, passing, in_progress, pending)
53    };
54
55    #[allow(clippy::cast_precision_loss)]
56    let progress_ratio = if total > 0 {
57        passing as f64 / total as f64
58    } else {
59        0.0
60    };
61
62    // Create layout
63    let chunks = Layout::default()
64        .direction(Direction::Vertical)
65        .margin(2)
66        .constraints([
67            Constraint::Length(3), // Progress bar
68            Constraint::Length(2), // Spacer
69            Constraint::Length(3), // Agent counts
70            Constraint::Length(2), // Spacer
71            Constraint::Min(5),    // Recent activity
72        ])
73        .split(inner);
74
75    // Progress gauge
76    let gauge_label = format!("{}/{} ({:.0}%)", passing, total, progress_ratio * 100.0);
77    let gauge = Gauge::default()
78        .block(Block::default())
79        .gauge_style(Style::default().fg(theme.done))
80        .label(gauge_label)
81        .ratio(progress_ratio);
82    frame.render_widget(gauge, chunks[0]);
83
84    // Agent counts (placeholder)
85    let agent_info = Paragraph::new(vec![Line::from(vec![
86        Span::styled(
87            format!("Coding Agents: {in_progress}/5     "),
88            Style::default().fg(theme.in_progress),
89        ),
90        Span::styled(
91            "Testing Agents: 1/5".to_string(),
92            Style::default().fg(theme.secondary),
93        ),
94    ])])
95    .alignment(Alignment::Left);
96    frame.render_widget(agent_info, chunks[2]);
97
98    // Recent activity
99    let activity = Paragraph::new(vec![
100        Line::from(Span::styled(
101            "Recent Activity:",
102            Style::default()
103                .fg(theme.foreground)
104                .add_modifier(Modifier::BOLD),
105        )),
106        Line::from(""),
107        Line::from(vec![
108            Span::styled("  • ", Style::default().fg(theme.done)),
109            Span::styled(
110                format!(
111                    "{} features completed",
112                    app.features.iter().filter(|f| f.passes).count()
113                ),
114                Style::default().fg(theme.foreground),
115            ),
116        ]),
117        Line::from(vec![
118            Span::styled("  • ", Style::default().fg(theme.in_progress)),
119            Span::styled(
120                format!(
121                    "{} features in progress",
122                    app.features.iter().filter(|f| f.in_progress).count()
123                ),
124                Style::default().fg(theme.foreground),
125            ),
126        ]),
127        Line::from(vec![
128            Span::styled("  • ", Style::default().fg(theme.pending)),
129            Span::styled(
130                format!(
131                    "{} features pending",
132                    app.features
133                        .iter()
134                        .filter(|f| !f.passes && !f.in_progress)
135                        .count()
136                ),
137                Style::default().fg(theme.foreground),
138            ),
139        ]),
140    ])
141    .alignment(Alignment::Left);
142    frame.render_widget(activity, chunks[4]);
143
144    // Render footer with key hints
145    let footer_text = if let Some(status) = app.get_status_message() {
146        status.to_string()
147    } else {
148        " 1-4:views  r:refresh  t:theme  ?:help  q:quit".to_string()
149    };
150
151    let footer = Paragraph::new(footer_text).style(Style::default().fg(theme.muted));
152    frame.render_widget(footer, footer_area);
153}