use std::fmt; use gpui::SharedString; use jiff::civil::Date; use crate::{mail::Label, primitives::IconName}; /// Where a task stands in its workflow. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Status { Backlog, Todo, InProgress, InReview, Done, Canceled, } impl Status { pub const ALL: [Status; 6] = [ Status::Backlog, Status::Todo, Status::InProgress, Status::InReview, Status::Done, Status::Canceled, ]; pub fn words(self) -> &'static str { match self { Status::Backlog => "Backlog", Status::Todo => "Todo", Status::InProgress => "In progress", Status::InReview => "In review", Status::Done => "Done", Status::Canceled => "Canceled", } } pub fn icon(self) -> IconName { match self { Status::Backlog => IconName::CircleDashed, Status::Todo => IconName::Circle, Status::InProgress => IconName::CircleDot, Status::InReview => IconName::CircleDotDashed, Status::Done => IconName::CircleCheck, Status::Canceled => IconName::CircleX, } } /// Done or canceled: nothing more is owed. pub fn closed(self) -> bool { matches!(self, Status::Done | Status::Canceled) } pub(crate) fn key(self) -> &'static str { match self { Status::Backlog => "backlog", Status::Todo => "todo", Status::InProgress => "in-progress", Status::InReview => "in-review", Status::Done => "done", Status::Canceled => "canceled", } } pub(crate) fn of_key(key: &str) -> Status { Status::ALL .into_iter() .find(|status| status.key() == key) .unwrap_or_else(|| panic!("no status {key}")) } } /// How urgent a task is, from none to urgent. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Priority { None, Low, Medium, High, Urgent, } impl Priority { /// Most urgent first. pub const ALL: [Priority; 5] = [ Priority::Urgent, Priority::High, Priority::Medium, Priority::Low, Priority::None, ]; pub fn words(self) -> &'static str { match self { Priority::None => "No priority", Priority::Low => "Low", Priority::Medium => "Medium", Priority::High => "High", Priority::Urgent => "Urgent", } } pub(crate) fn key(self) -> &'static str { match self { Priority::None => "none", Priority::Low => "low", Priority::Medium => "medium", Priority::High => "high", Priority::Urgent => "urgent", } } pub(crate) fn of_key(key: &str) -> Priority { Priority::ALL .into_iter() .find(|priority| priority.key() == key) .unwrap_or_else(|| panic!("no priority {key}")) } /// Bars lit of three; urgent has a mark of its own. pub(crate) fn bars(self) -> usize { match self { Priority::None => 0, Priority::Low => 1, Priority::Medium => 2, Priority::High | Priority::Urgent => 3, } } } /// An issue's name within its team: the team's key and a number, as ENG-123. #[derive(Clone, Debug, PartialEq, Eq)] pub struct IssueId { team: SharedString, number: u32, } impl IssueId { /// The key is one to five capital letters, and numbers start at one. pub fn new(team: impl Into, number: u32) -> Self { let team = team.into(); let letters = (1..=5).contains(&team.len()) && team.chars().all(|c| c.is_ascii_uppercase()); assert!(letters, "issue team key {team} is not one to five capitals"); assert!(number > 0, "issue {team} numbers start at one"); Self { team, number } } } impl fmt::Display for IssueId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}-{}", self.team, self.number) } } /// Someone work can go to. #[derive(Clone, Debug, PartialEq)] pub struct Person { pub key: SharedString, pub name: SharedString, } impl Person { pub fn new(key: impl Into, name: impl Into) -> Self { let key = key.into(); assert!(!key.is_empty(), "a person needs a key"); Self { key, name: name.into(), } } } /// A piece of work: its key, its issue's name if it has one, what it is, where it stands, how urgent, when it starts and is due, who has it, and its labels. #[derive(Clone, Debug, PartialEq)] pub struct Task { pub key: SharedString, pub issue: Option, pub title: SharedString, pub status: Status, pub priority: Priority, pub start: Option, pub due: Option, pub assignee: Option, pub labels: Vec