trackWork 0.14.2

A terminal-based time tracking application for managing work sessions
use chrono::{Local, NaiveDateTime};
use rand::Rng;

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct TimeEntry {
    pub id: i64,
    pub description: String,
    pub start_time: NaiveDateTime,
    pub end_time: Option<NaiveDateTime>,
    pub display_order: i64,
    pub color: u8,  // 0-5 for different colors
    pub issue_key: String,
    pub logged: bool,
    pub off_work: bool,
    /// Jira worklog id from the last successful log, used to delete it remotely.
    pub worklog_id: String,
}

impl TimeEntry {
    pub fn random_color() -> u8 {
        rand::thread_rng().gen_range(0..6)
    }

    /// Pick the best color based on usage statistics
    /// Colors get penalty points: +1 for each use, +1 extra if used most recently
    pub fn pick_best_color(color_usage: &[(u8, i64)]) -> u8 {
        const NUM_COLORS: u8 = 6;

        // Calculate penalty score for each color
        let mut scores = vec![0i64; NUM_COLORS as usize];

        for (idx, (color, count)) in color_usage.iter().enumerate() {
            let color_idx = *color as usize;
            if color_idx < NUM_COLORS as usize {
                // Add count penalty
                scores[color_idx] += count;
                // Add extra penalty if it's the most recent (first in the list)
                if idx == 0 {
                    scores[color_idx] += 1;
                }
            }
        }

        // Find color(s) with lowest score
        let min_score = scores.iter().min().copied().unwrap_or(0);
        let best_colors: Vec<u8> = scores
            .iter()
            .enumerate()
            .filter(|(_, &score)| score == min_score)
            .map(|(idx, _)| idx as u8)
            .collect();

        // Randomly pick one of the best colors
        if best_colors.is_empty() {
            Self::random_color()
        } else {
            let idx = rand::thread_rng().gen_range(0..best_colors.len());
            best_colors[idx]
        }
    }
}

#[derive(Debug, Clone)]
pub struct Task {
    pub id: i64,
    pub issue_key: String,
    pub name: String,
    pub project: String,
}

impl TimeEntry {
    pub fn duration_minutes(&self) -> Option<i64> {
        self.end_time.map(|end| {
            let duration = end.signed_duration_since(self.start_time);
            duration.num_minutes()
        })
    }

    pub fn duration_formatted(&self) -> String {
        let end = self.end_time.unwrap_or_else(|| Local::now().naive_local());
        let duration = end.signed_duration_since(self.start_time);

        let total_seconds = duration.num_seconds();
        let hours = total_seconds / 3600;
        let minutes = (total_seconds % 3600) / 60;
        let seconds = total_seconds % 60;

        if hours > 0 {
            format!("{}h {:02}m {:02}s", hours, minutes, seconds)
        } else if minutes > 0 {
            format!("{}m {:02}s", minutes, seconds)
        } else {
            format!("{}s", seconds)
        }
    }

    pub fn is_running(&self) -> bool {
        self.end_time.is_none()
    }
}