use super::cards;
use super::data::{DashboardData, Period};
use ratatui::{
Frame,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
text::Span,
widgets::{Block, Borders, Clear},
};
const CARD_COUNT: usize = 6;
#[derive(Debug, Clone)]
pub struct DashboardState {
pub period: Period,
pub focused_card: usize,
pub data: DashboardData,
}
impl Default for DashboardState {
fn default() -> Self {
Self {
period: Period::AllTime,
focused_card: 0,
data: DashboardData::default(),
}
}
}
impl DashboardState {
pub fn focus_next(&mut self) {
self.focused_card = (self.focused_card + 1) % CARD_COUNT;
}
pub fn focus_prev(&mut self) {
self.focused_card = if self.focused_card == 0 {
CARD_COUNT - 1
} else {
self.focused_card - 1
};
}
pub fn set_period(&mut self, period: Period) -> bool {
if self.period != period {
self.period = period;
true
} else {
false
}
}
}
pub(crate) fn centered_rect(area: Rect) -> Rect {
let w = (area.width * 3 / 4)
.max(60.min(area.width))
.min(area.width.saturating_sub(4));
let h = (area.height * 3 / 4)
.max(20.min(area.height))
.min(area.height.saturating_sub(2));
let x = area.x + (area.width.saturating_sub(w)) / 2;
let y = area.y + (area.height.saturating_sub(h)) / 2;
Rect::new(x, y, w, h)
}
pub fn render(f: &mut Frame, state: &DashboardState, area: Rect) {
let panel = centered_rect(area);
f.render_widget(Clear, panel);
let border = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Cyan))
.title(Span::styled(
" Usage Dashboard ",
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
));
let border_inner = border.inner(panel);
f.render_widget(border, panel);
let inner = Rect {
x: border_inner.x + 1,
y: border_inner.y,
width: border_inner.width.saturating_sub(2),
height: border_inner.height,
};
let activity_height = (inner.height / 4).clamp(5, 16);
let grid_min = if inner.height > 30 { 10 } else { 7 };
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(2), Constraint::Min(grid_min), Constraint::Length(activity_height), Constraint::Length(1), ])
.split(inner);
cards::render_summary(f, &state.data, chunks[0], state.period.label());
let mid_rows = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(chunks[1]);
let top_cols = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(mid_rows[0]);
let bot_cols = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(mid_rows[1]);
cards::render_daily(f, &state.data.daily, top_cols[0], state.focused_card == 0);
cards::render_projects(
f,
&state.data.projects,
top_cols[1],
state.focused_card == 1,
);
cards::render_models(f, &state.data.models, bot_cols[0], state.focused_card == 2);
cards::render_tools(f, &state.data.tools, bot_cols[1], state.focused_card == 3);
let bottom_row = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(75), Constraint::Percentage(25)])
.split(chunks[2]);
cards::render_activities(
f,
&state.data.activities,
bottom_row[0],
state.focused_card == 4,
);
cards::render_cache_efficiency(f, &state.data.cache, bottom_row[1], state.focused_card == 5);
cards::render_footer(f, chunks[3]);
}