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