sorting.rs

  1use crate::model::Task;
  2
  3/// Columns the task table can be sorted by.
  4#[derive(Clone, Copy, PartialEq, Eq)]
  5pub(in crate::cmd::webui) enum SortField {
  6    Id,
  7    Status,
  8    Type,
  9    Priority,
 10    Effort,
 11    Title,
 12    Created,
 13}
 14
 15impl SortField {
 16    pub(in crate::cmd::webui) fn parse(s: &str) -> Option<Self> {
 17        match s {
 18            "id" => Some(Self::Id),
 19            "status" => Some(Self::Status),
 20            "type" => Some(Self::Type),
 21            "priority" => Some(Self::Priority),
 22            "effort" => Some(Self::Effort),
 23            "title" => Some(Self::Title),
 24            "created" => Some(Self::Created),
 25            _ => None,
 26        }
 27    }
 28
 29    pub(in crate::cmd::webui) fn as_str(self) -> &'static str {
 30        match self {
 31            Self::Id => "id",
 32            Self::Status => "status",
 33            Self::Type => "type",
 34            Self::Priority => "priority",
 35            Self::Effort => "effort",
 36            Self::Title => "title",
 37            Self::Created => "created",
 38        }
 39    }
 40
 41    /// Sensible default direction when the user first clicks a column.
 42    pub(in crate::cmd::webui) fn default_order(self) -> SortOrder {
 43        match self {
 44            // Newest first, alphabetical ascending for text fields.
 45            Self::Created => SortOrder::Desc,
 46            Self::Title | Self::Id | Self::Type => SortOrder::Asc,
 47            // Highest priority/effort first; open before closed.
 48            Self::Priority | Self::Effort | Self::Status => SortOrder::Asc,
 49        }
 50    }
 51}
 52
 53#[derive(Clone, Copy, PartialEq, Eq)]
 54pub(in crate::cmd::webui) enum SortOrder {
 55    Asc,
 56    Desc,
 57}
 58
 59impl SortOrder {
 60    pub(in crate::cmd::webui) fn parse(s: &str) -> Option<Self> {
 61        match s {
 62            "asc" => Some(Self::Asc),
 63            "desc" => Some(Self::Desc),
 64            _ => None,
 65        }
 66    }
 67
 68    pub(in crate::cmd::webui) fn as_str(self) -> &'static str {
 69        match self {
 70            Self::Asc => "asc",
 71            Self::Desc => "desc",
 72        }
 73    }
 74}
 75
 76/// Map a `Status` to a numeric value for semantic sorting.
 77/// Lower values sort first in ascending order: open → in_progress → closed.
 78fn status_sort_key(s: crate::model::Status) -> i32 {
 79    match s {
 80        crate::model::Status::Open => 1,
 81        crate::model::Status::InProgress => 2,
 82        crate::model::Status::Closed => 3,
 83    }
 84}
 85
 86/// Apply the chosen sort field and direction to a filtered task list.
 87pub(in crate::cmd::webui) fn sort_tasks(tasks: &mut [&Task], field: SortField, order: SortOrder) {
 88    tasks.sort_by(|a, b| {
 89        let cmp = match field {
 90            SortField::Id => a.id.as_str().cmp(b.id.as_str()),
 91            SortField::Status => status_sort_key(a.status).cmp(&status_sort_key(b.status)),
 92            SortField::Type => a.task_type.cmp(&b.task_type),
 93            SortField::Priority => a.priority.score().cmp(&b.priority.score()),
 94            SortField::Effort => a.effort.score().cmp(&b.effort.score()),
 95            SortField::Title => a
 96                .title
 97                .to_ascii_lowercase()
 98                .cmp(&b.title.to_ascii_lowercase()),
 99            SortField::Created => a.created_at.cmp(&b.created_at),
100        };
101        match order {
102            SortOrder::Asc => cmp,
103            SortOrder::Desc => cmp.reverse(),
104        }
105    });
106}