hgame 0.26.4

CG production management structs, e.g. of assets, personnels, progress, etc.
Documentation
use super::*;

// ----------------------------------------------------------------------------
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "mongo", derive(Serialize, Deserialize))]
/// LEGACY DESIGN: DO NOT change field names or `serde(rename)` values.
pub struct SubTicketRawNote {
    #[serde(rename = "EN", skip_serializing_if = "Option::is_none")]
    pub(crate) en: Option<String>,

    #[serde(rename = "VI", skip_serializing_if = "Option::is_none")]
    pub(crate) vi: Option<String>,

    #[serde(rename = "ZH", skip_serializing_if = "Option::is_none")]
    pub(crate) zh: Option<String>,

    #[serde(rename = "FR", skip_serializing_if = "Option::is_none")]
    pub(crate) fr: Option<String>,
}

impl SubTicketRawNote {
    pub fn empty() -> Self {
        Default::default()
    }
}

// ----------------------------------------------------------------------------
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
/// LEGACY DESIGN: DO NOT change field names.
pub struct SubTicketRawStatus {
    #[serde(skip_serializing_if = "Option::is_none")]
    is_addressed: Option<bool>,

    #[serde(skip_serializing_if = "Option::is_none")]
    is_closed: Option<bool>,

    #[serde(skip_serializing_if = "Option::is_none")]
    last_modified_by: Option<String>,
}

impl SubTicketRawStatus {
    pub(super) fn empty() -> Self {
        Self {
            is_addressed: None,
            is_closed: None,
            last_modified_by: None,
        }
    }

    /// Makes a `SubTicket` whose status is "open", i.e. neither addressed nor closed.
    pub fn as_open(modified_by: &str) -> Self {
        Self {
            // from deserialization logic, leaving this as `None` suffices
            is_addressed: None,
            // from deserialization logic, leaving this as `None` suffices
            is_closed: None,
            last_modified_by: Some(modified_by.to_owned()),
        }
    }

    /// If a `SubTicket`'s Closed status `is_none`, it's considered NOT closed.
    ///
    /// CAUTION: this is "baked" and its data is not synced with database.
    pub(super) fn is_literal_closed(&self) -> bool {
        self.is_closed.unwrap_or(false)
    }

    /// If a `SubTicket`'s Addressed status `is_none`, it's considered NOT addressed.
    ///
    /// CAUTION: this is "baked" and its data is not synced with database.
    fn is_literal_addressed(&self) -> bool {
        self.is_addressed.unwrap_or(false)
    }

    /// CAUTION: this is "baked" and its data is not synced with database.
    pub(super) fn is_literal_open(&self) -> bool {
        match self.is_addressed {
            None => true,
            Some(addressed) => !addressed,
        }
    }
}

// ----------------------------------------------------------------------------
#[derive(Debug, Clone, Default, PartialEq, Eq, strum::AsRefStr)]
pub enum SubTicketStatus {
    #[strum(serialize = "💬 Open")]
    #[default]
    Open,

    #[strum(serialize = "🙋 Addressed")]
    Addressed,

    #[strum(serialize = "✅ Closed")]
    Closed,
}

#[cfg(feature = "gui")]
impl SubTicketStatus {
    fn color_rgb<'a>(&self, palette: &'a Palette) -> &'a Rgb {
        match &self {
            SubTicketStatus::Open => &palette.dark.rgb.TICKET_OPEN,
            SubTicketStatus::Addressed => &palette.dark.rgb.TICKET_ADDRESSED,
            SubTicketStatus::Closed => &palette.dark.rgb.TICKET_CLOSED,
        }
    }

    pub fn color32(&self, palette: &Palette) -> Color32 {
        let color = self.color_rgb(palette);
        Color32::from_rgb(color.r, color.g, color.b)
    }

    pub(super) fn mark_open_button(&self, idx: &u8) -> egui::Button {
        match &self {
            SubTicketStatus::Addressed => egui::Button::new(&format!("#{} Not Addressed", idx)),
            SubTicketStatus::Closed => egui::Button::new(&format!("Reopen #{}", idx)),
            SubTicketStatus::Open => unreachable!(), // shouldn't be used
        }
    }
}

impl From<&SubTicketRawStatus> for SubTicketStatus {
    /// CAUTION: Both `SubTicketRawStatus::is_literal_closed()` and `SubTicketRawStatus::is_addressed()`
    /// are "baked" and thus `SubTicketStatus` converted here is NOT automatically synced with database.
    fn from(status: &SubTicketRawStatus) -> Self {
        match status.is_literal_closed() {
            true => SubTicketStatus::Closed,
            false => match status.is_literal_addressed() {
                true => SubTicketStatus::Addressed,
                false => SubTicketStatus::Open,
            },
        }
    }
}