pub mod widgets;
pub mod views;
use ratatui::{
layout::{Constraint, Layout, Rect},
style::{Color, Style, Stylize},
text::{Line, Span},
Frame,
};
use chrono::Datelike;
use crate::app::{App, Tab};
use crate::i18n;
use crate::llm::LlmReportType;
pub fn render(frame: &mut Frame, app: &App) {
let area = frame.area();
let main_chunks = Layout::vertical([
Constraint::Length(1), Constraint::Length(1), Constraint::Min(3), Constraint::Length(1), ])
.split(area);
render_title_bar(frame, main_chunks[0], app);
render_tab_bar(frame, main_chunks[1], app);
render_content(frame, main_chunks[2], app);
render_status_bar(frame, main_chunks[3], app);
}
fn render_title_bar(frame: &mut Frame, area: Rect, app: &App) {
let t = i18n::t();
let title = match app.active_tab {
Tab::Summary => format!(
"{} | {} | First session: {}",
t.title_dashboard,
t.title_all_time,
app.stats
.first_session_date
.as_deref()
.unwrap_or("N/A")
),
Tab::Monthly => {
let y = app.selected_month.year();
let m = app.selected_month.month();
let month_str = chrono::Month::try_from(m as u8)
.map(|mo| format!("{:?}", mo))
.unwrap_or_else(|_| m.to_string());
format!("{} | {} {}", t.title_dashboard, month_str, y)
}
Tab::Yearly => format!("{} | {}", t.title_dashboard, app.selected_year),
Tab::LlmSummary => {
let label = match app.llm.report_type {
LlmReportType::All => t.title_all_time.to_string(),
LlmReportType::Monthly => {
let m = chrono::Month::try_from(app.llm.selected_month.month() as u8)
.map(|mo| format!("{:?}", mo))
.unwrap_or_else(|_| app.llm.selected_month.month().to_string());
format!("{} {}", m, app.llm.selected_month.year())
}
LlmReportType::Yearly => app.llm.selected_year.to_string(),
};
format!("{} | {} ({})", t.title_dashboard, t.title_llm_summary, label)
}
};
let line = Line::from(vec![Span::styled(
title,
Style::default().fg(Color::Reset),
)]);
let p = ratatui::widgets::Paragraph::new(line)
.style(Style::default().bg(Color::Reset));
frame.render_widget(p, area);
}
fn render_tab_bar(frame: &mut Frame, area: Rect, app: &App) {
let t = i18n::t();
let tabs = vec![
(t.tab_overview, Tab::Summary),
(t.tab_monthly, Tab::Monthly),
(t.tab_yearly, Tab::Yearly),
(t.tab_llm, Tab::LlmSummary),
];
let spans: Vec<Span> = tabs
.iter()
.map(|(label, tab)| {
if *tab == app.active_tab {
Span::styled(
format!(" {} ", label),
Style::default().fg(Color::Black).bg(Color::Cyan).bold(),
)
} else {
Span::styled(
format!(" {} ", label),
Style::default().fg(Color::Gray).bg(Color::Reset),
)
}
})
.collect();
let line = Line::from(spans);
let p = ratatui::widgets::Paragraph::new(line)
.style(Style::default().bg(Color::Reset));
frame.render_widget(p, area);
}
fn render_content(frame: &mut Frame, area: Rect, app: &App) {
match app.active_tab {
Tab::Summary => views::render_summary_view(frame, area, app),
Tab::Monthly => views::render_monthly_view(frame, area, app),
Tab::Yearly => views::render_yearly_view(frame, area, app),
Tab::LlmSummary => views::render_llm_summary_view(frame, area, app),
}
}
fn render_status_bar(frame: &mut Frame, area: Rect, app: &App) {
let t = i18n::t();
let parse_errors = app.parse_error_count;
let errors_str = if parse_errors > 0 {
format!(" | {}: {}", t.status_parse_errors, parse_errors)
} else {
String::new()
};
let loading_str = if app.load_state == crate::app::LoadState::Loading {
format!(" | {}", t.status_loading)
} else {
String::new()
};
let hint = if app.active_tab == Tab::LlmSummary {
format!(" | {}", t.status_llm_hint)
} else {
String::new()
};
let status = format!(
"{} | {} ({}) | {} | {} | {} | {}{}{}{}",
t.status_scroll,
t.status_tab_focus,
app.ui.focus_section.name(),
t.status_navigate,
t.status_copy,
t.status_refresh,
t.status_quit,
errors_str, loading_str, hint
);
let line = Line::from(vec![Span::styled(
status,
Style::default().fg(Color::DarkGray).bg(Color::Reset),
)]);
let p = ratatui::widgets::Paragraph::new(line)
.style(Style::default().bg(Color::Reset));
frame.render_widget(p, area);
}