1use 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
12pub struct DashboardView;
14
15impl DashboardView {
16 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), Constraint::Length(8), Constraint::Min(0), ])
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 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 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 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 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}