bitbucket_cli/tui/views/
dashboard.rs

1/// Dashboard view - main overview of workspace
2use ratatui::{
3    Frame,
4    layout::{Constraint, Direction, Layout, Rect},
5    style::{Color, Modifier, Style},
6    text::{Line, Span},
7    widgets::{Block, Borders, List, ListItem, Paragraph},
8};
9
10use crate::tui::app::App;
11
12/// Dashboard widget showing workspace overview
13pub struct DashboardView;
14
15impl DashboardView {
16    /// Render the dashboard
17    pub fn render(f: &mut Frame, app: &App, area: Rect) {
18        let chunks = Layout::default()
19            .direction(Direction::Vertical)
20            .constraints([
21                Constraint::Length(5), // Header with workspace info
22                Constraint::Length(8), // Quick stats
23                Constraint::Min(0),    // Quick access menu
24            ])
25            .split(area);
26
27        Self::render_header(f, app, chunks[0]);
28        Self::render_stats(f, app, chunks[1]);
29        Self::render_menu(f, app, chunks[2]);
30    }
31
32    fn render_header(f: &mut Frame, app: &App, area: Rect) {
33        let workspace_info = match &app.workspace {
34            Some(ws) => vec![
35                Line::from(vec![
36                    Span::styled("Workspace: ", Style::default().fg(Color::DarkGray)),
37                    Span::styled(
38                        ws,
39                        Style::default()
40                            .fg(Color::Cyan)
41                            .add_modifier(Modifier::BOLD),
42                    ),
43                ]),
44                Line::from(""),
45                Line::from(Span::styled(
46                    "Welcome to Bitbucket CLI TUI",
47                    Style::default().fg(Color::White),
48                )),
49            ],
50            None => vec![
51                Line::from(Span::styled(
52                    "No workspace selected",
53                    Style::default().fg(Color::Yellow),
54                )),
55                Line::from(""),
56                Line::from(Span::styled(
57                    "Use --workspace flag or set a default workspace",
58                    Style::default().fg(Color::DarkGray),
59                )),
60            ],
61        };
62
63        let header = Paragraph::new(workspace_info).block(
64            Block::default()
65                .borders(Borders::ALL)
66                .title(" 🌐 Bitbucket "),
67        );
68        f.render_widget(header, area);
69    }
70
71    fn render_stats(f: &mut Frame, app: &App, area: Rect) {
72        let stats_layout = Layout::default()
73            .direction(Direction::Horizontal)
74            .constraints([
75                Constraint::Percentage(25),
76                Constraint::Percentage(25),
77                Constraint::Percentage(25),
78                Constraint::Percentage(25),
79            ])
80            .split(area);
81
82        // Repositories stat
83        let repos_stat = Paragraph::new(vec![
84            Line::from(Span::styled(
85                format!("{}", app.repositories.len()),
86                Style::default()
87                    .fg(Color::Cyan)
88                    .add_modifier(Modifier::BOLD),
89            )),
90            Line::from(Span::styled(
91                "Repositories",
92                Style::default().fg(Color::DarkGray),
93            )),
94        ])
95        .block(Block::default().borders(Borders::ALL).title(" 📁 "));
96        f.render_widget(repos_stat, stats_layout[0]);
97
98        // Pull requests stat
99        let open_prs = app
100            .pull_requests
101            .iter()
102            .filter(|pr| pr.state == crate::models::PullRequestState::Open)
103            .count();
104        let prs_stat = Paragraph::new(vec![
105            Line::from(Span::styled(
106                format!("{}", open_prs),
107                Style::default()
108                    .fg(Color::Green)
109                    .add_modifier(Modifier::BOLD),
110            )),
111            Line::from(Span::styled(
112                "Open PRs",
113                Style::default().fg(Color::DarkGray),
114            )),
115        ])
116        .block(Block::default().borders(Borders::ALL).title(" 🔀 "));
117        f.render_widget(prs_stat, stats_layout[1]);
118
119        // Issues stat
120        let open_issues = app
121            .issues
122            .iter()
123            .filter(|i| {
124                i.state == crate::models::IssueState::Open
125                    || i.state == crate::models::IssueState::New
126            })
127            .count();
128        let issues_stat = Paragraph::new(vec![
129            Line::from(Span::styled(
130                format!("{}", open_issues),
131                Style::default()
132                    .fg(Color::Yellow)
133                    .add_modifier(Modifier::BOLD),
134            )),
135            Line::from(Span::styled(
136                "Open Issues",
137                Style::default().fg(Color::DarkGray),
138            )),
139        ])
140        .block(Block::default().borders(Borders::ALL).title(" 🐛 "));
141        f.render_widget(issues_stat, stats_layout[2]);
142
143        // Pipelines stat
144        let running_pipelines = app
145            .pipelines
146            .iter()
147            .filter(|p| p.state.name == crate::models::PipelineStateName::Building)
148            .count();
149        let pipelines_stat = Paragraph::new(vec![
150            Line::from(Span::styled(
151                format!("{}", running_pipelines),
152                Style::default()
153                    .fg(Color::Blue)
154                    .add_modifier(Modifier::BOLD),
155            )),
156            Line::from(Span::styled(
157                "Running",
158                Style::default().fg(Color::DarkGray),
159            )),
160        ])
161        .block(Block::default().borders(Borders::ALL).title(" ⚙️ "));
162        f.render_widget(pipelines_stat, stats_layout[3]);
163    }
164
165    fn render_menu(f: &mut Frame, app: &App, area: Rect) {
166        let items: Vec<ListItem> = vec![
167            ListItem::new(Line::from(vec![
168                Span::styled("📁 ", Style::default()),
169                Span::styled(
170                    "Repositories",
171                    Style::default().add_modifier(Modifier::BOLD),
172                ),
173                Span::styled(
174                    " - Browse and manage repositories",
175                    Style::default().fg(Color::DarkGray),
176                ),
177            ])),
178            ListItem::new(Line::from(vec![
179                Span::styled("🔀 ", Style::default()),
180                Span::styled(
181                    "Pull Requests",
182                    Style::default().add_modifier(Modifier::BOLD),
183                ),
184                Span::styled(
185                    " - Review and merge code",
186                    Style::default().fg(Color::DarkGray),
187                ),
188            ])),
189            ListItem::new(Line::from(vec![
190                Span::styled("🐛 ", Style::default()),
191                Span::styled("Issues", Style::default().add_modifier(Modifier::BOLD)),
192                Span::styled(
193                    " - Track bugs and tasks",
194                    Style::default().fg(Color::DarkGray),
195                ),
196            ])),
197            ListItem::new(Line::from(vec![
198                Span::styled("⚙️  ", Style::default()),
199                Span::styled("Pipelines", Style::default().add_modifier(Modifier::BOLD)),
200                Span::styled(
201                    " - Monitor CI/CD builds",
202                    Style::default().fg(Color::DarkGray),
203                ),
204            ])),
205        ];
206
207        let list = List::new(items)
208            .block(
209                Block::default()
210                    .borders(Borders::ALL)
211                    .title(" Quick Access "),
212            )
213            .highlight_style(
214                Style::default()
215                    .bg(Color::DarkGray)
216                    .add_modifier(Modifier::BOLD),
217            )
218            .highlight_symbol("▶ ");
219
220        let mut state = ratatui::widgets::ListState::default();
221        state.select(Some(app.view_state.selected_index));
222        f.render_stateful_widget(list, area, &mut state);
223    }
224}