tempo_cli/ui/
widgets.rs

1use chrono::{DateTime, Local};
2use ratatui::{
3    style::{Color, Style},
4    widgets::{Block, BorderType, Borders},
5};
6
7use crate::ui::formatter::Formatter;
8
9pub struct StatusWidget;
10pub struct ProgressWidget;
11pub struct SummaryWidget;
12
13// Centralized color scheme with a Neon/Cyberpunk aesthetic
14pub struct ColorScheme;
15
16impl ColorScheme {
17    // Vibrant Colors
18    pub const NEON_CYAN: Color = Color::Rgb(0, 255, 255);
19    pub const NEON_GREEN: Color = Color::Rgb(57, 255, 20);
20    pub const NEON_PINK: Color = Color::Rgb(255, 16, 240);
21    pub const NEON_PURPLE: Color = Color::Rgb(188, 19, 254);
22    pub const NEON_YELLOW: Color = Color::Rgb(255, 240, 31);
23    pub const DARK_BG: Color = Color::Rgb(10, 10, 15);
24    pub const GRAY_TEXT: Color = Color::Rgb(160, 160, 160);
25    pub const WHITE_TEXT: Color = Color::Rgb(240, 240, 240);
26
27    pub fn get_context_color(context: &str) -> Color {
28        match context {
29            "terminal" => Self::NEON_CYAN,
30            "ide" => Self::NEON_PURPLE,
31            "linked" => Self::NEON_YELLOW,
32            "manual" => Color::Blue,
33            _ => Self::WHITE_TEXT,
34        }
35    }
36
37    pub fn active_status() -> Color {
38        Self::NEON_GREEN
39    }
40    pub fn project_name() -> Color {
41        Self::NEON_YELLOW
42    }
43    pub fn duration() -> Color {
44        Self::NEON_CYAN
45    }
46    pub fn path() -> Color {
47        Self::GRAY_TEXT
48    }
49    pub fn timestamp() -> Color {
50        Self::GRAY_TEXT
51    }
52    pub fn border() -> Color {
53        Self::NEON_PURPLE
54    }
55    pub fn title() -> Color {
56        Self::NEON_PINK
57    }
58
59    pub fn base_block() -> Block<'static> {
60        Block::default()
61            .borders(Borders::ALL)
62            .border_style(Style::default().fg(Self::border()))
63            .border_type(BorderType::Rounded)
64            .style(Style::default().bg(Self::DARK_BG))
65    }
66}
67
68pub struct Spinner {
69    frames: Vec<&'static str>,
70    current: usize,
71}
72
73impl Spinner {
74    pub fn new() -> Self {
75        Self {
76            frames: vec!["-", "\\", "|", "/"],
77            current: 0,
78        }
79    }
80
81    pub fn next(&mut self) -> &'static str {
82        let frame = self.frames[self.current];
83        self.current = (self.current + 1) % self.frames.len();
84        frame
85    }
86
87    pub fn current(&self) -> &'static str {
88        self.frames[self.current]
89    }
90}
91
92impl StatusWidget {
93    pub fn render_status_text(
94        project_name: &str,
95        duration: i64,
96        start_time: &str,
97        context: &str,
98    ) -> String {
99        format!(
100            "* ACTIVE | {} | Time: {} | Started: {} | Context: {}",
101            project_name,
102            Formatter::format_duration(duration),
103            start_time,
104            context
105        )
106    }
107
108    pub fn render_idle_text() -> String {
109        "- IDLE | No active time tracking session | Use 'tempo session start' to begin tracking"
110            .to_string()
111    }
112}
113
114impl ProgressWidget {
115    pub fn calculate_daily_progress(completed_seconds: i64, target_hours: f64) -> u16 {
116        let total_hours = completed_seconds as f64 / 3600.0;
117        let progress = (total_hours / target_hours * 100.0).min(100.0) as u16;
118        progress
119    }
120
121    pub fn format_progress_label(completed_seconds: i64, target_hours: f64) -> String {
122        let total_hours = completed_seconds as f64 / 3600.0;
123        let progress = (total_hours / target_hours * 100.0).min(100.0) as u16;
124        format!(
125            "Daily Progress ({:.1}h / {:.1}h) - {}%",
126            total_hours, target_hours, progress
127        )
128    }
129}
130
131impl SummaryWidget {
132    pub fn format_project_summary(
133        project_name: &str,
134        total_time: i64,
135        session_count: usize,
136        active_count: usize,
137    ) -> String {
138        format!(
139            "Project: {} | Total Time: {} | Sessions: {} total, {} active",
140            project_name,
141            Formatter::format_duration(total_time),
142            session_count,
143            active_count
144        )
145    }
146
147    pub fn format_session_line(
148        start_time: &DateTime<Local>,
149        duration: i64,
150        context: &str,
151        is_active: bool,
152    ) -> String {
153        let status_char = if is_active { "*" } else { "+" };
154        let duration_str = if is_active {
155            format!("{} (active)", Formatter::format_duration(duration))
156        } else {
157            Formatter::format_duration(duration)
158        };
159
160        format!(
161            "{} {} | {} | {}",
162            status_char,
163            start_time.format("%H:%M:%S"),
164            duration_str,
165            context
166        )
167    }
168}